From 7aeef6aab561bd76f4b5e457c20de0be02bfde55 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 14 Dec 2020 18:29:04 +0900 Subject: [PATCH 001/232] Add OWNERS for packages/Connectivity In preparation of future modularization of connectivity classes, a large part of frameworks/base/services/net is planned to be moved to packages/modules/Connectivity. However moving each class "manually" to that git project would lose their commit history, and require many cross-repository topics. To facilitate the work, create frameworks/base/packages/Connectivity, which will be used to move the classes at first, before they can all be moved together with their history to packages/modules/Connectivity. This mirrors the procedure used for NetworkStack, Tethering, CaptivePortalLogin, etc. previously. The added OWNERS file just imports owners from the previous file location. Bug: 171540887 Test: m Change-Id: Ic1c0dbbe8c1f07582c04757ced0ead2fc5b10ca7 --- OWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 OWNERS diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000..48e54da82c --- /dev/null +++ b/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS From 59262cbcdfbf5939e6cce16b54f984281d0f6015 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 21 Dec 2020 18:40:08 +0900 Subject: [PATCH 002/232] Split connectivity JNI and service jar The VPN JNI code is moving to the tethering APEX with ConnectivityService, so it needs to be split out of libandroid_servers. Also move the service-connectivity.jar build rule to packages/Connectivity together with the jni build rule. Bug: 171540887 Test: m, device boots and VPN (L2TP and VpnService) verified working Change-Id: Ic29096e2280ce928729315f53b2159b620da49d5 --- service/Android.bp | 79 ++++ service/jarjar-rules.txt | 1 + .../com_android_server_TestNetworkService.cpp | 107 +++++ .../com_android_server_connectivity_Vpn.cpp | 377 ++++++++++++++++++ service/jni/onload.cpp | 40 ++ 5 files changed, 604 insertions(+) create mode 100644 service/Android.bp create mode 100644 service/jarjar-rules.txt create mode 100644 service/jni/com_android_server_TestNetworkService.cpp create mode 100644 service/jni/com_android_server_connectivity_Vpn.cpp create mode 100644 service/jni/onload.cpp diff --git a/service/Android.bp b/service/Android.bp new file mode 100644 index 0000000000..a26f715280 --- /dev/null +++ b/service/Android.bp @@ -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. +// + +cc_defaults { + name: "libservice-connectivity-defaults", + // TODO: build against the NDK (sdk_version: "30" for example) + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + srcs: [ + "jni/com_android_server_TestNetworkService.cpp", + "jni/com_android_server_connectivity_Vpn.cpp", + ], + shared_libs: [ + "libbase", + "liblog", + "libnativehelper", + // TODO: remove dependency on ifc_[add/del]_address by having Java code to add/delete + // addresses, and remove dependency on libnetutils. + "libnetutils", + ], +} + +cc_library_shared { + name: "libservice-connectivity", + defaults: ["libservice-connectivity-defaults"], + srcs: [ + "jni/onload.cpp", + ], + apex_available: [ + // TODO: move this library to the tethering APEX and remove libservice-connectivity-static + // "com.android.tethering", + ], +} + +// Static library linked into libservices.core until libservice-connectivity can be loaded from +// the tethering APEX instead. +cc_library_static { + name: "libservice-connectivity-static", + defaults: ["libservice-connectivity-defaults"], +} + +java_library { + name: "service-connectivity", + srcs: [ + ":connectivity-service-srcs", + ], + installable: true, + jarjar_rules: "jarjar-rules.txt", + libs: [ + "android.net.ipsec.ike", + "services.core", + "services.net", + "unsupportedappusage", + ], + static_libs: [ + "net-utils-device-common", + "net-utils-framework-common", + ], + apex_available: [ + "//apex_available:platform", + ], +} diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt new file mode 100644 index 0000000000..ef53ebb43c --- /dev/null +++ b/service/jarjar-rules.txt @@ -0,0 +1 @@ +rule com.android.net.module.util.** com.android.connectivity.util.@1 \ No newline at end of file diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp new file mode 100644 index 0000000000..36a6fde361 --- /dev/null +++ b/service/jni/com_android_server_TestNetworkService.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_NDEBUG 0 + +#define LOG_TAG "TestNetworkServiceJni" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "netutils/ifc.h" + +#include "jni.h" +#include +#include +#include +#include + +namespace android { + +//------------------------------------------------------------------------------ + +static void throwException(JNIEnv* env, int error, const char* action, const char* iface) { + const std::string& msg = + android::base::StringPrintf("Error %s %s: %s", action, iface, strerror(error)); + + jniThrowException(env, "java/lang/IllegalStateException", msg.c_str()); +} + +static int createTunTapInterface(JNIEnv* env, bool isTun, const char* iface) { + base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK)); + ifreq ifr{}; + + // Allocate interface. + ifr.ifr_flags = (isTun ? IFF_TUN : IFF_TAP) | IFF_NO_PI; + strlcpy(ifr.ifr_name, iface, IFNAMSIZ); + if (ioctl(tun.get(), TUNSETIFF, &ifr)) { + throwException(env, errno, "allocating", ifr.ifr_name); + return -1; + } + + // Activate interface using an unconnected datagram socket. + base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0)); + ifr.ifr_flags = IFF_UP; + + if (ioctl(inet6CtrlSock.get(), SIOCSIFFLAGS, &ifr)) { + throwException(env, errno, "activating", ifr.ifr_name); + return -1; + } + + return tun.release(); +} + +//------------------------------------------------------------------------------ + +static jint create(JNIEnv* env, jobject /* thiz */, jboolean isTun, jstring jIface) { + ScopedUtfChars iface(env, jIface); + if (!iface.c_str()) { + jniThrowNullPointerException(env, "iface"); + return -1; + } + + int tun = createTunTapInterface(env, isTun, iface.c_str()); + + // Any exceptions will be thrown from the createTunTapInterface call + return tun; +} + +//------------------------------------------------------------------------------ + +static const JNINativeMethod gMethods[] = { + {"jniCreateTunTap", "(ZLjava/lang/String;)I", (void*)create}, +}; + +int register_android_server_TestNetworkService(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/TestNetworkService", gMethods, + NELEM(gMethods)); +} + +}; // namespace android diff --git a/service/jni/com_android_server_connectivity_Vpn.cpp b/service/jni/com_android_server_connectivity_Vpn.cpp new file mode 100644 index 0000000000..ea5e7183c9 --- /dev/null +++ b/service/jni/com_android_server_connectivity_Vpn.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_NDEBUG 0 + +#define LOG_TAG "VpnJni" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "netutils/ifc.h" + +#include "jni.h" +#include + +namespace android +{ + +static int inet4 = -1; +static int inet6 = -1; + +static inline in_addr_t *as_in_addr(sockaddr *sa) { + return &((sockaddr_in *)sa)->sin_addr.s_addr; +} + +//------------------------------------------------------------------------------ + +#define SYSTEM_ERROR (-1) +#define BAD_ARGUMENT (-2) + +static int create_interface(int mtu) +{ + int tun = open("/dev/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC); + + ifreq ifr4; + memset(&ifr4, 0, sizeof(ifr4)); + + // Allocate interface. + ifr4.ifr_flags = IFF_TUN | IFF_NO_PI; + if (ioctl(tun, TUNSETIFF, &ifr4)) { + ALOGE("Cannot allocate TUN: %s", strerror(errno)); + goto error; + } + + // Activate interface. + ifr4.ifr_flags = IFF_UP; + if (ioctl(inet4, SIOCSIFFLAGS, &ifr4)) { + ALOGE("Cannot activate %s: %s", ifr4.ifr_name, strerror(errno)); + goto error; + } + + // Set MTU if it is specified. + ifr4.ifr_mtu = mtu; + if (mtu > 0 && ioctl(inet4, SIOCSIFMTU, &ifr4)) { + ALOGE("Cannot set MTU on %s: %s", ifr4.ifr_name, strerror(errno)); + goto error; + } + + return tun; + +error: + close(tun); + return SYSTEM_ERROR; +} + +static int get_interface_name(char *name, int tun) +{ + ifreq ifr4; + if (ioctl(tun, TUNGETIFF, &ifr4)) { + ALOGE("Cannot get interface name: %s", strerror(errno)); + return SYSTEM_ERROR; + } + strncpy(name, ifr4.ifr_name, IFNAMSIZ); + return 0; +} + +static int get_interface_index(const char *name) +{ + ifreq ifr4; + strncpy(ifr4.ifr_name, name, IFNAMSIZ); + if (ioctl(inet4, SIOGIFINDEX, &ifr4)) { + ALOGE("Cannot get index of %s: %s", name, strerror(errno)); + return SYSTEM_ERROR; + } + return ifr4.ifr_ifindex; +} + +static int set_addresses(const char *name, const char *addresses) +{ + int index = get_interface_index(name); + if (index < 0) { + return index; + } + + ifreq ifr4; + memset(&ifr4, 0, sizeof(ifr4)); + strncpy(ifr4.ifr_name, name, IFNAMSIZ); + ifr4.ifr_addr.sa_family = AF_INET; + ifr4.ifr_netmask.sa_family = AF_INET; + + in6_ifreq ifr6; + memset(&ifr6, 0, sizeof(ifr6)); + ifr6.ifr6_ifindex = index; + + char address[65]; + int prefix; + int chars; + int count = 0; + + while (sscanf(addresses, " %64[^/]/%d %n", address, &prefix, &chars) == 2) { + addresses += chars; + + if (strchr(address, ':')) { + // Add an IPv6 address. + if (inet_pton(AF_INET6, address, &ifr6.ifr6_addr) != 1 || + prefix < 0 || prefix > 128) { + count = BAD_ARGUMENT; + break; + } + + ifr6.ifr6_prefixlen = prefix; + if (ioctl(inet6, SIOCSIFADDR, &ifr6)) { + count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; + break; + } + } else { + // Add an IPv4 address. + if (inet_pton(AF_INET, address, as_in_addr(&ifr4.ifr_addr)) != 1 || + prefix < 0 || prefix > 32) { + count = BAD_ARGUMENT; + break; + } + + if (count) { + snprintf(ifr4.ifr_name, sizeof(ifr4.ifr_name), "%s:%d", name, count); + } + if (ioctl(inet4, SIOCSIFADDR, &ifr4)) { + count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; + break; + } + + in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0; + *as_in_addr(&ifr4.ifr_netmask) = htonl(mask); + if (ioctl(inet4, SIOCSIFNETMASK, &ifr4)) { + count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; + break; + } + } + ALOGD("Address added on %s: %s/%d", name, address, prefix); + ++count; + } + + if (count == BAD_ARGUMENT) { + ALOGE("Invalid address: %s/%d", address, prefix); + } else if (count == SYSTEM_ERROR) { + ALOGE("Cannot add address: %s/%d: %s", address, prefix, strerror(errno)); + } else if (*addresses) { + ALOGE("Invalid address: %s", addresses); + count = BAD_ARGUMENT; + } + + return count; +} + +static int reset_interface(const char *name) +{ + ifreq ifr4; + strncpy(ifr4.ifr_name, name, IFNAMSIZ); + ifr4.ifr_flags = 0; + + if (ioctl(inet4, SIOCSIFFLAGS, &ifr4) && errno != ENODEV) { + ALOGE("Cannot reset %s: %s", name, strerror(errno)); + return SYSTEM_ERROR; + } + return 0; +} + +static int check_interface(const char *name) +{ + ifreq ifr4; + strncpy(ifr4.ifr_name, name, IFNAMSIZ); + ifr4.ifr_flags = 0; + + if (ioctl(inet4, SIOCGIFFLAGS, &ifr4) && errno != ENODEV) { + ALOGE("Cannot check %s: %s", name, strerror(errno)); + } + return ifr4.ifr_flags; +} + +static bool modifyAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, + jint jPrefixLength, bool add) +{ + int error = SYSTEM_ERROR; + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; + const char *address = jAddress ? env->GetStringUTFChars(jAddress, NULL) : NULL; + + if (!name) { + jniThrowNullPointerException(env, "name"); + } else if (!address) { + jniThrowNullPointerException(env, "address"); + } else { + if (add) { + if ((error = ifc_add_address(name, address, jPrefixLength)) != 0) { + ALOGE("Cannot add address %s/%d on interface %s (%s)", address, jPrefixLength, name, + strerror(-error)); + } + } else { + if ((error = ifc_del_address(name, address, jPrefixLength)) != 0) { + ALOGE("Cannot del address %s/%d on interface %s (%s)", address, jPrefixLength, name, + strerror(-error)); + } + } + } + + if (name) { + env->ReleaseStringUTFChars(jName, name); + } + if (address) { + env->ReleaseStringUTFChars(jAddress, address); + } + return !error; +} + +//------------------------------------------------------------------------------ + +static void throwException(JNIEnv *env, int error, const char *message) +{ + if (error == SYSTEM_ERROR) { + jniThrowException(env, "java/lang/IllegalStateException", message); + } else { + jniThrowException(env, "java/lang/IllegalArgumentException", message); + } +} + +static jint create(JNIEnv *env, jobject /* thiz */, jint mtu) +{ + int tun = create_interface(mtu); + if (tun < 0) { + throwException(env, tun, "Cannot create interface"); + return -1; + } + return tun; +} + +static jstring getName(JNIEnv *env, jobject /* thiz */, jint tun) +{ + char name[IFNAMSIZ]; + if (get_interface_name(name, tun) < 0) { + throwException(env, SYSTEM_ERROR, "Cannot get interface name"); + return NULL; + } + return env->NewStringUTF(name); +} + +static jint setAddresses(JNIEnv *env, jobject /* thiz */, jstring jName, + jstring jAddresses) +{ + const char *name = NULL; + const char *addresses = NULL; + int count = -1; + + name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; + if (!name) { + jniThrowNullPointerException(env, "name"); + goto error; + } + addresses = jAddresses ? env->GetStringUTFChars(jAddresses, NULL) : NULL; + if (!addresses) { + jniThrowNullPointerException(env, "addresses"); + goto error; + } + count = set_addresses(name, addresses); + if (count < 0) { + throwException(env, count, "Cannot set address"); + count = -1; + } + +error: + if (name) { + env->ReleaseStringUTFChars(jName, name); + } + if (addresses) { + env->ReleaseStringUTFChars(jAddresses, addresses); + } + return count; +} + +static void reset(JNIEnv *env, jobject /* thiz */, jstring jName) +{ + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; + if (!name) { + jniThrowNullPointerException(env, "name"); + return; + } + if (reset_interface(name) < 0) { + throwException(env, SYSTEM_ERROR, "Cannot reset interface"); + } + env->ReleaseStringUTFChars(jName, name); +} + +static jint check(JNIEnv *env, jobject /* thiz */, jstring jName) +{ + const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; + if (!name) { + jniThrowNullPointerException(env, "name"); + return 0; + } + int flags = check_interface(name); + env->ReleaseStringUTFChars(jName, name); + return flags; +} + +static bool addAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, + jint jPrefixLength) +{ + return modifyAddress(env, thiz, jName, jAddress, jPrefixLength, true); +} + +static bool delAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, + jint jPrefixLength) +{ + return modifyAddress(env, thiz, jName, jAddress, jPrefixLength, false); +} + +//------------------------------------------------------------------------------ + +static const JNINativeMethod gMethods[] = { + {"jniCreate", "(I)I", (void *)create}, + {"jniGetName", "(I)Ljava/lang/String;", (void *)getName}, + {"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses}, + {"jniReset", "(Ljava/lang/String;)V", (void *)reset}, + {"jniCheck", "(Ljava/lang/String;)I", (void *)check}, + {"jniAddAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)addAddress}, + {"jniDelAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)delAddress}, +}; + +int register_android_server_connectivity_Vpn(JNIEnv *env) +{ + if (inet4 == -1) { + inet4 = socket(AF_INET, SOCK_DGRAM, 0); + } + if (inet6 == -1) { + inet6 = socket(AF_INET6, SOCK_DGRAM, 0); + } + return jniRegisterNativeMethods(env, "com/android/server/connectivity/Vpn", + gMethods, NELEM(gMethods)); +} + +}; diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp new file mode 100644 index 0000000000..3afcb0e8f6 --- /dev/null +++ b/service/jni/onload.cpp @@ -0,0 +1,40 @@ +/* + * 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 +#include + +namespace android { + +int register_android_server_connectivity_Vpn(JNIEnv* env); +int register_android_server_TestNetworkService(JNIEnv* env); + +extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + ALOGE("GetEnv failed"); + return JNI_ERR; + } + + if (register_android_server_connectivity_Vpn(env) < 0 + || register_android_server_TestNetworkService(env) < 0) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} + +}; \ No newline at end of file From c2d6a94b35c18fc239a936884de749fa81bb6136 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 23 Oct 2020 17:57:31 +0900 Subject: [PATCH 003/232] Move service-connectivity to the tethering APEX As part of modularization of ConnectivityService and expansion of the Tethering module scope, move service-connectivity.jar into the tethering APEX, and load it from there. Bug: 171540887 Test: m, device boots and connectivity Change-Id: Id7b6a4664ae73224b9ab219c94f56d603a62ee5a --- service/Android.bp | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/service/Android.bp b/service/Android.bp index a26f715280..c8f3bd3666 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -14,8 +14,8 @@ // limitations under the License. // -cc_defaults { - name: "libservice-connectivity-defaults", +cc_library_shared { + name: "libservice-connectivity", // TODO: build against the NDK (sdk_version: "30" for example) cflags: [ "-Wall", @@ -26,6 +26,7 @@ cc_defaults { srcs: [ "jni/com_android_server_TestNetworkService.cpp", "jni/com_android_server_connectivity_Vpn.cpp", + "jni/onload.cpp", ], shared_libs: [ "libbase", @@ -35,27 +36,11 @@ cc_defaults { // addresses, and remove dependency on libnetutils. "libnetutils", ], -} - -cc_library_shared { - name: "libservice-connectivity", - defaults: ["libservice-connectivity-defaults"], - srcs: [ - "jni/onload.cpp", - ], apex_available: [ - // TODO: move this library to the tethering APEX and remove libservice-connectivity-static - // "com.android.tethering", + "com.android.tethering", ], } -// Static library linked into libservices.core until libservice-connectivity can be loaded from -// the tethering APEX instead. -cc_library_static { - name: "libservice-connectivity-static", - defaults: ["libservice-connectivity-defaults"], -} - java_library { name: "service-connectivity", srcs: [ @@ -75,5 +60,6 @@ java_library { ], apex_available: [ "//apex_available:platform", + "com.android.tethering", ], } From b145611a1e7738468083e2205460f5e8ec9ff6ea Mon Sep 17 00:00:00 2001 From: Pete Bentley Date: Thu, 7 Jan 2021 13:51:18 +0000 Subject: [PATCH 004/232] Revert "Move service-connectivity to the tethering APEX" Revert submission 1532910-connectivity_jar_in_apex Reason for revert: Breaks boot tests: b/176969905 Reverted Changes: Ie41a5b569:Set setCurrentProxyScriptUrl as public Id7b6a4664:Move service-connectivity to the tethering APEX Ia7cb83834:Add service-connectivity to tethering APEX Change-Id: I1c369dd8a6527513f8fc1a5cacde59d78d104c7e --- service/Android.bp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/service/Android.bp b/service/Android.bp index c8f3bd3666..a26f715280 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -14,8 +14,8 @@ // limitations under the License. // -cc_library_shared { - name: "libservice-connectivity", +cc_defaults { + name: "libservice-connectivity-defaults", // TODO: build against the NDK (sdk_version: "30" for example) cflags: [ "-Wall", @@ -26,7 +26,6 @@ cc_library_shared { srcs: [ "jni/com_android_server_TestNetworkService.cpp", "jni/com_android_server_connectivity_Vpn.cpp", - "jni/onload.cpp", ], shared_libs: [ "libbase", @@ -36,9 +35,25 @@ cc_library_shared { // addresses, and remove dependency on libnetutils. "libnetutils", ], - apex_available: [ - "com.android.tethering", +} + +cc_library_shared { + name: "libservice-connectivity", + defaults: ["libservice-connectivity-defaults"], + srcs: [ + "jni/onload.cpp", ], + apex_available: [ + // TODO: move this library to the tethering APEX and remove libservice-connectivity-static + // "com.android.tethering", + ], +} + +// Static library linked into libservices.core until libservice-connectivity can be loaded from +// the tethering APEX instead. +cc_library_static { + name: "libservice-connectivity-static", + defaults: ["libservice-connectivity-defaults"], } java_library { @@ -60,6 +75,5 @@ java_library { ], apex_available: [ "//apex_available:platform", - "com.android.tethering", ], } From eaa9658100c28c73d63e57ed139533148e67a3b9 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 8 Jan 2021 01:19:44 +0000 Subject: [PATCH 005/232] Revert^2 "Move service-connectivity to the tethering APEX" As part of modularization of ConnectivityService and expansion of the Tethering module scope, move service-connectivity.jar into the tethering APEX, and load it from there. This rolls forward the change. The original topic was reverted because of a bad merged-in clause; this is fixed and re-verified in this topic. Bug: 171540887 Test: m, device boots and connectivity Change-Id: I293b09c0dc04c6ccafa30cd0f1a63efe32283604 --- service/Android.bp | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/service/Android.bp b/service/Android.bp index a26f715280..c8f3bd3666 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -14,8 +14,8 @@ // limitations under the License. // -cc_defaults { - name: "libservice-connectivity-defaults", +cc_library_shared { + name: "libservice-connectivity", // TODO: build against the NDK (sdk_version: "30" for example) cflags: [ "-Wall", @@ -26,6 +26,7 @@ cc_defaults { srcs: [ "jni/com_android_server_TestNetworkService.cpp", "jni/com_android_server_connectivity_Vpn.cpp", + "jni/onload.cpp", ], shared_libs: [ "libbase", @@ -35,27 +36,11 @@ cc_defaults { // addresses, and remove dependency on libnetutils. "libnetutils", ], -} - -cc_library_shared { - name: "libservice-connectivity", - defaults: ["libservice-connectivity-defaults"], - srcs: [ - "jni/onload.cpp", - ], apex_available: [ - // TODO: move this library to the tethering APEX and remove libservice-connectivity-static - // "com.android.tethering", + "com.android.tethering", ], } -// Static library linked into libservices.core until libservice-connectivity can be loaded from -// the tethering APEX instead. -cc_library_static { - name: "libservice-connectivity-static", - defaults: ["libservice-connectivity-defaults"], -} - java_library { name: "service-connectivity", srcs: [ @@ -75,5 +60,6 @@ java_library { ], apex_available: [ "//apex_available:platform", + "com.android.tethering", ], } From ef337535cee0ae73e673d8c09ebe8212f8966881 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Mon, 18 Jan 2021 11:58:24 +0800 Subject: [PATCH 006/232] Use function inside NetdUtils instead of NetworkManagementService TestNetworkService will be a part of mainline module, to prevent using @hide API of NetworkManagementService in TestNetworkService, use function inside NetdUtils instead. Bug: 170598012 Test: atest CtsNetTestCasesLatestSdk Change-Id: I738066ce2f1bcd616dc582a3ff1fd77bcd660c73 --- service/Android.bp | 1 + 1 file changed, 1 insertion(+) diff --git a/service/Android.bp b/service/Android.bp index c8f3bd3666..8fc3181807 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -57,6 +57,7 @@ java_library { static_libs: [ "net-utils-device-common", "net-utils-framework-common", + "netd-client", ], apex_available: [ "//apex_available:platform", From 66ea68e472a1c16b49da48874ffebfbc2bcdf098 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 15 Jan 2021 16:22:36 +0900 Subject: [PATCH 007/232] Move connectivity-sources to frameworks/base All sources for the connectivity module are being moved to frameworks/base/packages/Connectivity, so that they can all be moved to packages/modules/Connectivity in one move keeping history. INetworkAgent and INetworkAgentRegistry were created in packages/modules/Connectivity directly, but this makes managing the sources until the move harder as the module needs to pull sources from two different locations. Considering that they do not have history to move, just move them to frameworks/base/packages/Connectivity without the commit history. Bug: 171540887 Test: m Change-Id: Ic4353115a98b6235c2b5d5ce24741223e618d0a6 --- framework/Android.bp | 29 +++++++++++ .../connectivity/aidl/INetworkAgent.aidl | 49 +++++++++++++++++++ .../aidl/INetworkAgentRegistry.aidl | 41 ++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 framework/Android.bp create mode 100644 framework/src/com/android/connectivity/aidl/INetworkAgent.aidl create mode 100644 framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl diff --git a/framework/Android.bp b/framework/Android.bp new file mode 100644 index 0000000000..8db8d7699a --- /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 0000000000..64b556720c --- /dev/null +++ b/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl @@ -0,0 +1,49 @@ +/** + * 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.QosFilterParcelable; +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); + void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel); + void onQosCallbackUnregistered(int qosCallbackId); +} 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 0000000000..f0193db5c2 --- /dev/null +++ b/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl @@ -0,0 +1,41 @@ +/** + * 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; +import android.net.QosSession; +import android.telephony.data.EpsBearerQosSessionAttributes; + +/** + * 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 networks); + void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes); + void sendQosSessionLost(int qosCallbackId, in QosSession session); + void sendQosCallbackError(int qosCallbackId, int exceptionType); +} From fbbccbce69e782c3994fb1231c1126ea5b0b057d Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 15 Jan 2021 18:08:24 +0900 Subject: [PATCH 008/232] Move module sources to packages/Connectivity Files that are planned to be part of the connectivity module are grouped in packages/Connectivity, so they can be built separately and moved in one operation with their history into packages/modules/Connectivity. This places the files in the existing framework-connectivity-sources filegroup instead of the current framework-core-sources filegroup. Both are used the same way in framework-non-updatable-sources. Bug: 171540887 Test: m Change-Id: I62d9d91574ace6f5c4624035d190260c3126b91e --- framework/src/android/net/CaptivePortal.java | 171 + .../src/android/net/CaptivePortalData.aidl | 19 + .../src/android/net/CaptivePortalData.java | 305 + framework/src/android/net/ConnectionInfo.aidl | 20 + framework/src/android/net/ConnectionInfo.java | 83 + .../net/ConnectivityDiagnosticsManager.aidl | 21 + .../net/ConnectivityDiagnosticsManager.java | 777 +++ .../src/android/net/ConnectivityManager.java | 5058 +++++++++++++++++ .../android/net/ConnectivityMetricsEvent.aidl | 20 + .../src/android/net/ConnectivityThread.java | 56 + framework/src/android/net/DhcpInfo.aidl | 19 + framework/src/android/net/DhcpInfo.java | 105 + framework/src/android/net/DnsResolver.java | 577 ++ framework/src/android/net/ICaptivePortal.aidl | 27 + .../net/IConnectivityDiagnosticsCallback.aidl | 28 + .../src/android/net/IConnectivityManager.aidl | 246 + .../android/net/ISocketKeepaliveCallback.aidl | 34 + .../src/android/net/ITestNetworkManager.aidl | 39 + framework/src/android/net/InetAddresses.java | 65 + .../android/net/InterfaceConfiguration.aidl | 19 + .../android/net/InvalidPacketException.java | 66 + .../src/android/net/IpConfiguration.aidl | 19 + .../src/android/net/IpConfiguration.java | 223 + framework/src/android/net/IpPrefix.aidl | 22 + framework/src/android/net/IpPrefix.java | 300 + .../src/android/net/KeepalivePacketData.aidl | 19 + .../src/android/net/KeepalivePacketData.java | 119 + framework/src/android/net/LinkAddress.aidl | 21 + framework/src/android/net/LinkAddress.java | 549 ++ framework/src/android/net/LinkProperties.aidl | 20 + framework/src/android/net/LinkProperties.java | 1823 ++++++ framework/src/android/net/MacAddress.aidl | 20 + framework/src/android/net/MacAddress.java | 400 ++ .../android/net/NattKeepalivePacketData.java | 144 + .../src/android/net/NattSocketKeepalive.java | 77 + framework/src/android/net/Network.aidl | 20 + framework/src/android/net/Network.java | 535 ++ framework/src/android/net/NetworkAgent.java | 1185 ++++ .../src/android/net/NetworkAgentConfig.aidl | 19 + .../src/android/net/NetworkAgentConfig.java | 440 ++ .../src/android/net/NetworkCapabilities.aidl | 21 + .../src/android/net/NetworkCapabilities.java | 2517 ++++++++ framework/src/android/net/NetworkConfig.java | 80 + framework/src/android/net/NetworkInfo.aidl | 19 + framework/src/android/net/NetworkInfo.java | 626 ++ .../src/android/net/NetworkProvider.java | 162 + framework/src/android/net/NetworkRequest.aidl | 20 + framework/src/android/net/NetworkRequest.java | 582 ++ framework/src/android/net/NetworkUtils.java | 425 ++ .../src/android/net/PacProxySelector.java | 138 + framework/src/android/net/Proxy.java | 294 + framework/src/android/net/ProxyInfo.aidl | 21 + framework/src/android/net/ProxyInfo.java | 367 ++ framework/src/android/net/RouteInfo.aidl | 19 + framework/src/android/net/RouteInfo.java | 658 +++ .../src/android/net/SocketKeepalive.java | 301 + .../android/net/StaticIpConfiguration.aidl | 20 + .../android/net/StaticIpConfiguration.java | 332 ++ .../android/net/TcpKeepalivePacketData.java | 163 + .../src/android/net/TcpRepairWindow.java | 48 + .../src/android/net/TcpSocketKeepalive.java | 77 + .../src/android/net/TestNetworkInterface.aidl | 20 + .../src/android/net/TestNetworkInterface.java | 78 + .../src/android/net/TestNetworkManager.java | 178 + framework/src/android/net/TransportInfo.java | 63 + framework/src/android/net/UidRange.aidl | 24 + framework/src/android/net/VpnManager.java | 164 + framework/src/android/net/VpnService.java | 903 +++ .../src/android/net/apf/ApfCapabilities.aidl | 20 + .../src/android/net/apf/ApfCapabilities.java | 133 + framework/src/android/net/util/DnsUtils.java | 379 ++ .../src/android/net/util/KeepaliveUtils.java | 115 + .../net/util/MultinetworkPolicyTracker.java | 217 + .../src/android/net/util/SocketUtils.java | 121 + 74 files changed, 23015 insertions(+) create mode 100644 framework/src/android/net/CaptivePortal.java create mode 100644 framework/src/android/net/CaptivePortalData.aidl create mode 100644 framework/src/android/net/CaptivePortalData.java create mode 100644 framework/src/android/net/ConnectionInfo.aidl create mode 100644 framework/src/android/net/ConnectionInfo.java create mode 100644 framework/src/android/net/ConnectivityDiagnosticsManager.aidl create mode 100644 framework/src/android/net/ConnectivityDiagnosticsManager.java create mode 100644 framework/src/android/net/ConnectivityManager.java create mode 100644 framework/src/android/net/ConnectivityMetricsEvent.aidl create mode 100644 framework/src/android/net/ConnectivityThread.java create mode 100644 framework/src/android/net/DhcpInfo.aidl create mode 100644 framework/src/android/net/DhcpInfo.java create mode 100644 framework/src/android/net/DnsResolver.java create mode 100644 framework/src/android/net/ICaptivePortal.aidl create mode 100644 framework/src/android/net/IConnectivityDiagnosticsCallback.aidl create mode 100644 framework/src/android/net/IConnectivityManager.aidl create mode 100644 framework/src/android/net/ISocketKeepaliveCallback.aidl create mode 100644 framework/src/android/net/ITestNetworkManager.aidl create mode 100644 framework/src/android/net/InetAddresses.java create mode 100644 framework/src/android/net/InterfaceConfiguration.aidl create mode 100644 framework/src/android/net/InvalidPacketException.java create mode 100644 framework/src/android/net/IpConfiguration.aidl create mode 100644 framework/src/android/net/IpConfiguration.java create mode 100644 framework/src/android/net/IpPrefix.aidl create mode 100644 framework/src/android/net/IpPrefix.java create mode 100644 framework/src/android/net/KeepalivePacketData.aidl create mode 100644 framework/src/android/net/KeepalivePacketData.java create mode 100644 framework/src/android/net/LinkAddress.aidl create mode 100644 framework/src/android/net/LinkAddress.java create mode 100644 framework/src/android/net/LinkProperties.aidl create mode 100644 framework/src/android/net/LinkProperties.java create mode 100644 framework/src/android/net/MacAddress.aidl create mode 100644 framework/src/android/net/MacAddress.java create mode 100644 framework/src/android/net/NattKeepalivePacketData.java create mode 100644 framework/src/android/net/NattSocketKeepalive.java create mode 100644 framework/src/android/net/Network.aidl create mode 100644 framework/src/android/net/Network.java create mode 100644 framework/src/android/net/NetworkAgent.java create mode 100644 framework/src/android/net/NetworkAgentConfig.aidl create mode 100644 framework/src/android/net/NetworkAgentConfig.java create mode 100644 framework/src/android/net/NetworkCapabilities.aidl create mode 100644 framework/src/android/net/NetworkCapabilities.java create mode 100644 framework/src/android/net/NetworkConfig.java create mode 100644 framework/src/android/net/NetworkInfo.aidl create mode 100644 framework/src/android/net/NetworkInfo.java create mode 100644 framework/src/android/net/NetworkProvider.java create mode 100644 framework/src/android/net/NetworkRequest.aidl create mode 100644 framework/src/android/net/NetworkRequest.java create mode 100644 framework/src/android/net/NetworkUtils.java create mode 100644 framework/src/android/net/PacProxySelector.java create mode 100644 framework/src/android/net/Proxy.java create mode 100644 framework/src/android/net/ProxyInfo.aidl create mode 100644 framework/src/android/net/ProxyInfo.java create mode 100644 framework/src/android/net/RouteInfo.aidl create mode 100644 framework/src/android/net/RouteInfo.java create mode 100644 framework/src/android/net/SocketKeepalive.java create mode 100644 framework/src/android/net/StaticIpConfiguration.aidl create mode 100644 framework/src/android/net/StaticIpConfiguration.java create mode 100644 framework/src/android/net/TcpKeepalivePacketData.java create mode 100644 framework/src/android/net/TcpRepairWindow.java create mode 100644 framework/src/android/net/TcpSocketKeepalive.java create mode 100644 framework/src/android/net/TestNetworkInterface.aidl create mode 100644 framework/src/android/net/TestNetworkInterface.java create mode 100644 framework/src/android/net/TestNetworkManager.java create mode 100644 framework/src/android/net/TransportInfo.java create mode 100644 framework/src/android/net/UidRange.aidl create mode 100644 framework/src/android/net/VpnManager.java create mode 100644 framework/src/android/net/VpnService.java create mode 100644 framework/src/android/net/apf/ApfCapabilities.aidl create mode 100644 framework/src/android/net/apf/ApfCapabilities.java create mode 100644 framework/src/android/net/util/DnsUtils.java create mode 100644 framework/src/android/net/util/KeepaliveUtils.java create mode 100644 framework/src/android/net/util/MultinetworkPolicyTracker.java create mode 100644 framework/src/android/net/util/SocketUtils.java diff --git a/framework/src/android/net/CaptivePortal.java b/framework/src/android/net/CaptivePortal.java new file mode 100644 index 0000000000..269bbf20c8 --- /dev/null +++ b/framework/src/android/net/CaptivePortal.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed urnder 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; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +/** + * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN} + * activity to indicate to the system different outcomes of captive portal sign in. This class is + * passed as an extra named {@link ConnectivityManager#EXTRA_CAPTIVE_PORTAL} with the + * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity. + */ +public class CaptivePortal implements Parcelable { + /** + * Response code from the captive portal application, indicating that the portal was dismissed + * and the network should be re-validated. + * @see ICaptivePortal#appResponse(int) + * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int) + * @hide + */ + @SystemApi + public static final int APP_RETURN_DISMISSED = 0; + /** + * Response code from the captive portal application, indicating that the user did not login and + * does not want to use the captive portal network. + * @see ICaptivePortal#appResponse(int) + * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int) + * @hide + */ + @SystemApi + public static final int APP_RETURN_UNWANTED = 1; + /** + * Response code from the captive portal application, indicating that the user does not wish to + * login but wants to use the captive portal network as-is. + * @see ICaptivePortal#appResponse(int) + * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int) + * @hide + */ + @SystemApi + public static final int APP_RETURN_WANTED_AS_IS = 2; + /** Event offset of request codes from captive portal application. */ + private static final int APP_REQUEST_BASE = 100; + /** + * Request code from the captive portal application, indicating that the network condition may + * have changed and the network should be re-validated. + * @see ICaptivePortal#appRequest(int) + * @see android.net.INetworkMonitor#forceReevaluation(int) + * @hide + */ + @SystemApi + public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0; + + private final IBinder mBinder; + + /** @hide */ + public CaptivePortal(@NonNull IBinder binder) { + mBinder = binder; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeStrongBinder(mBinder); + } + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public CaptivePortal createFromParcel(Parcel in) { + return new CaptivePortal(in.readStrongBinder()); + } + + @Override + public CaptivePortal[] newArray(int size) { + return new CaptivePortal[size]; + } + }; + + /** + * Indicate to the system that the captive portal has been + * dismissed. In response the framework will re-evaluate the network's + * connectivity and might take further action thereafter. + */ + public void reportCaptivePortalDismissed() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_DISMISSED); + } catch (RemoteException e) { + } + } + + /** + * Indicate to the system that the user does not want to pursue signing in to the + * captive portal and the system should continue to prefer other networks + * without captive portals for use as the default active data network. The + * system will not retest the network for a captive portal so as to avoid + * disturbing the user with further sign in to network notifications. + */ + public void ignoreNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_UNWANTED); + } catch (RemoteException e) { + } + } + + /** + * Indicate to the system the user wants to use this network as is, even though + * the captive portal is still in place. The system will treat the network + * as if it did not have a captive portal when selecting the network to use + * as the default active data network. This may result in this network + * becoming the default active data network, which could disrupt network + * connectivity for apps because the captive portal is still in place. + * @hide + */ + @SystemApi + public void useNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS); + } catch (RemoteException e) { + } + } + + /** + * Request that the system reevaluates the captive portal status. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void reevaluateNetwork() { + try { + ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED); + } catch (RemoteException e) { + } + } + + /** + * Log a captive portal login event. + * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto. + * @param packageName captive portal application package name. + * @hide + */ + @SystemApi + public void logEvent(int eventId, @NonNull String packageName) { + try { + ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName); + } catch (RemoteException e) { + } + } +} diff --git a/framework/src/android/net/CaptivePortalData.aidl b/framework/src/android/net/CaptivePortalData.aidl new file mode 100644 index 0000000000..1d57ee7591 --- /dev/null +++ b/framework/src/android/net/CaptivePortalData.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +@JavaOnlyStableParcelable parcelable CaptivePortalData; diff --git a/framework/src/android/net/CaptivePortalData.java b/framework/src/android/net/CaptivePortalData.java new file mode 100644 index 0000000000..18467fad8e --- /dev/null +++ b/framework/src/android/net/CaptivePortalData.java @@ -0,0 +1,305 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Metadata sent by captive portals, see https://www.ietf.org/id/draft-ietf-capport-api-03.txt. + * @hide + */ +@SystemApi +public final class CaptivePortalData implements Parcelable { + private final long mRefreshTimeMillis; + @Nullable + private final Uri mUserPortalUrl; + @Nullable + private final Uri mVenueInfoUrl; + private final boolean mIsSessionExtendable; + private final long mByteLimit; + private final long mExpiryTimeMillis; + private final boolean mCaptive; + private final String mVenueFriendlyName; + + private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, + boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive, + String venueFriendlyName) { + mRefreshTimeMillis = refreshTimeMillis; + mUserPortalUrl = userPortalUrl; + mVenueInfoUrl = venueInfoUrl; + mIsSessionExtendable = isSessionExtendable; + mByteLimit = byteLimit; + mExpiryTimeMillis = expiryTimeMillis; + mCaptive = captive; + mVenueFriendlyName = venueFriendlyName; + } + + private CaptivePortalData(Parcel p) { + this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(), + p.readLong(), p.readLong(), p.readBoolean(), p.readString()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mRefreshTimeMillis); + dest.writeParcelable(mUserPortalUrl, 0); + dest.writeParcelable(mVenueInfoUrl, 0); + dest.writeBoolean(mIsSessionExtendable); + dest.writeLong(mByteLimit); + dest.writeLong(mExpiryTimeMillis); + dest.writeBoolean(mCaptive); + dest.writeString(mVenueFriendlyName); + } + + /** + * A builder to create new {@link CaptivePortalData}. + */ + public static class Builder { + private long mRefreshTime; + private Uri mUserPortalUrl; + private Uri mVenueInfoUrl; + private boolean mIsSessionExtendable; + private long mBytesRemaining = -1; + private long mExpiryTime = -1; + private boolean mCaptive; + private String mVenueFriendlyName; + + /** + * Create an empty builder. + */ + public Builder() {} + + /** + * Create a builder copying all data from existing {@link CaptivePortalData}. + */ + public Builder(@Nullable CaptivePortalData data) { + if (data == null) return; + setRefreshTime(data.mRefreshTimeMillis) + .setUserPortalUrl(data.mUserPortalUrl) + .setVenueInfoUrl(data.mVenueInfoUrl) + .setSessionExtendable(data.mIsSessionExtendable) + .setBytesRemaining(data.mByteLimit) + .setExpiryTime(data.mExpiryTimeMillis) + .setCaptive(data.mCaptive) + .setVenueFriendlyName(data.mVenueFriendlyName); + } + + /** + * Set the time at which data was last refreshed, as per {@link System#currentTimeMillis()}. + */ + @NonNull + public Builder setRefreshTime(long refreshTime) { + mRefreshTime = refreshTime; + return this; + } + + /** + * Set the URL to be used for users to login to the portal, if captive. + */ + @NonNull + public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) { + mUserPortalUrl = userPortalUrl; + return this; + } + + /** + * Set the URL that can be used by users to view information about the network venue. + */ + @NonNull + public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) { + mVenueInfoUrl = venueInfoUrl; + return this; + } + + /** + * Set whether the portal supports extending a user session on the portal URL page. + */ + @NonNull + public Builder setSessionExtendable(boolean sessionExtendable) { + mIsSessionExtendable = sessionExtendable; + return this; + } + + /** + * Set the number of bytes remaining on the network before the portal closes. + */ + @NonNull + public Builder setBytesRemaining(long bytesRemaining) { + mBytesRemaining = bytesRemaining; + return this; + } + + /** + * Set the time at the session will expire, as per {@link System#currentTimeMillis()}. + */ + @NonNull + public Builder setExpiryTime(long expiryTime) { + mExpiryTime = expiryTime; + return this; + } + + /** + * Set whether the network is captive (portal closed). + */ + @NonNull + public Builder setCaptive(boolean captive) { + mCaptive = captive; + return this; + } + + /** + * Set the venue friendly name. + */ + @NonNull + public Builder setVenueFriendlyName(@Nullable String venueFriendlyName) { + mVenueFriendlyName = venueFriendlyName; + return this; + } + + /** + * Create a new {@link CaptivePortalData}. + */ + @NonNull + public CaptivePortalData build() { + return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl, + mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive, + mVenueFriendlyName); + } + } + + /** + * Get the time at which data was last refreshed, as per {@link System#currentTimeMillis()}. + */ + public long getRefreshTimeMillis() { + return mRefreshTimeMillis; + } + + /** + * Get the URL to be used for users to login to the portal, or extend their session if + * {@link #isSessionExtendable()} is true. + */ + @Nullable + public Uri getUserPortalUrl() { + return mUserPortalUrl; + } + + /** + * Get the URL that can be used by users to view information about the network venue. + */ + @Nullable + public Uri getVenueInfoUrl() { + return mVenueInfoUrl; + } + + /** + * Indicates whether the user portal URL can be used to extend sessions, when the user is logged + * in and the session has a time or byte limit. + */ + public boolean isSessionExtendable() { + return mIsSessionExtendable; + } + + /** + * Get the remaining bytes on the captive portal session, at the time {@link CaptivePortalData} + * was refreshed. This may be different from the limit currently enforced by the portal. + * @return The byte limit, or -1 if not set. + */ + public long getByteLimit() { + return mByteLimit; + } + + /** + * Get the time at the session will expire, as per {@link System#currentTimeMillis()}. + * @return The expiry time, or -1 if unset. + */ + public long getExpiryTimeMillis() { + return mExpiryTimeMillis; + } + + /** + * Get whether the network is captive (portal closed). + */ + public boolean isCaptive() { + return mCaptive; + } + + /** + * Get the venue friendly name + */ + @Nullable + public String getVenueFriendlyName() { + return mVenueFriendlyName; + } + + @NonNull + public static final Creator CREATOR = new Creator() { + @Override + public CaptivePortalData createFromParcel(Parcel source) { + return new CaptivePortalData(source); + } + + @Override + public CaptivePortalData[] newArray(int size) { + return new CaptivePortalData[size]; + } + }; + + @Override + public int hashCode() { + return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl, + mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CaptivePortalData)) return false; + final CaptivePortalData other = (CaptivePortalData) obj; + return mRefreshTimeMillis == other.mRefreshTimeMillis + && Objects.equals(mUserPortalUrl, other.mUserPortalUrl) + && Objects.equals(mVenueInfoUrl, other.mVenueInfoUrl) + && mIsSessionExtendable == other.mIsSessionExtendable + && mByteLimit == other.mByteLimit + && mExpiryTimeMillis == other.mExpiryTimeMillis + && mCaptive == other.mCaptive + && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName); + } + + @Override + public String toString() { + return "CaptivePortalData {" + + "refreshTime: " + mRefreshTimeMillis + + ", userPortalUrl: " + mUserPortalUrl + + ", venueInfoUrl: " + mVenueInfoUrl + + ", isSessionExtendable: " + mIsSessionExtendable + + ", byteLimit: " + mByteLimit + + ", expiryTime: " + mExpiryTimeMillis + + ", captive: " + mCaptive + + ", venueFriendlyName: " + mVenueFriendlyName + + "}"; + } +} diff --git a/framework/src/android/net/ConnectionInfo.aidl b/framework/src/android/net/ConnectionInfo.aidl new file mode 100644 index 0000000000..07faf8bbbe --- /dev/null +++ b/framework/src/android/net/ConnectionInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright (C) 2018 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; + +parcelable ConnectionInfo; diff --git a/framework/src/android/net/ConnectionInfo.java b/framework/src/android/net/ConnectionInfo.java new file mode 100644 index 0000000000..4514a8484d --- /dev/null +++ b/framework/src/android/net/ConnectionInfo.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 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; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +/** + * Describe a network connection including local and remote address/port of a connection and the + * transport protocol. + * + * @hide + */ +public final class ConnectionInfo implements Parcelable { + public final int protocol; + public final InetSocketAddress local; + public final InetSocketAddress remote; + + @Override + public int describeContents() { + return 0; + } + + public ConnectionInfo(int protocol, InetSocketAddress local, InetSocketAddress remote) { + this.protocol = protocol; + this.local = local; + this.remote = remote; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(protocol); + out.writeByteArray(local.getAddress().getAddress()); + out.writeInt(local.getPort()); + out.writeByteArray(remote.getAddress().getAddress()); + out.writeInt(remote.getPort()); + } + + public static final @android.annotation.NonNull Creator CREATOR = new Creator() { + public ConnectionInfo createFromParcel(Parcel in) { + int protocol = in.readInt(); + InetAddress localAddress; + try { + localAddress = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Invalid InetAddress"); + } + int localPort = in.readInt(); + InetAddress remoteAddress; + try { + remoteAddress = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("Invalid InetAddress"); + } + int remotePort = in.readInt(); + InetSocketAddress local = new InetSocketAddress(localAddress, localPort); + InetSocketAddress remote = new InetSocketAddress(remoteAddress, remotePort); + return new ConnectionInfo(protocol, local, remote); + } + + public ConnectionInfo[] newArray(int size) { + return new ConnectionInfo[size]; + } + }; +} diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.aidl b/framework/src/android/net/ConnectivityDiagnosticsManager.aidl new file mode 100644 index 0000000000..82ba0ca113 --- /dev/null +++ b/framework/src/android/net/ConnectivityDiagnosticsManager.aidl @@ -0,0 +1,21 @@ +/** + * + * 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 android.net; + +parcelable ConnectivityDiagnosticsManager.ConnectivityReport; +parcelable ConnectivityDiagnosticsManager.DataStallReport; \ No newline at end of file diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.java b/framework/src/android/net/ConnectivityDiagnosticsManager.java new file mode 100644 index 0000000000..5234494973 --- /dev/null +++ b/framework/src/android/net/ConnectivityDiagnosticsManager.java @@ -0,0 +1,777 @@ +/* + * 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; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.content.Context; +import android.os.Binder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import android.os.RemoteException; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +/** + * Class that provides utilities for collecting network connectivity diagnostics information. + * Connectivity information is made available through triggerable diagnostics tools and by listening + * to System validations. Some diagnostics information may be permissions-restricted. + * + *

ConnectivityDiagnosticsManager is intended for use by applications offering network + * connectivity on a user device. These tools will provide several mechanisms for these applications + * to be alerted to network conditions as well as diagnose potential network issues themselves. + * + *

The primary responsibilities of this class are to: + * + *

    + *
  • Allow permissioned applications to register and unregister callbacks for network event + * notifications + *
  • Invoke callbacks for network event notifications, including: + *
      + *
    • Network validations + *
    • Data stalls + *
    • Connectivity reports from applications + *
    + *
+ */ +public class ConnectivityDiagnosticsManager { + /** @hide */ + @VisibleForTesting + public static final Map + sCallbacks = new ConcurrentHashMap<>(); + + private final Context mContext; + private final IConnectivityManager mService; + + /** @hide */ + public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) { + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + } + + /** @hide */ + @VisibleForTesting + public static boolean persistableBundleEquals( + @Nullable PersistableBundle a, @Nullable PersistableBundle b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (!Objects.equals(a.keySet(), b.keySet())) return false; + for (String key : a.keySet()) { + if (!Objects.equals(a.get(key), b.get(key))) return false; + } + return true; + } + + /** Class that includes connectivity information for a specific Network at a specific time. */ + public static final class ConnectivityReport implements Parcelable { + /** + * The overall status of the network is that it is invalid; it neither provides + * connectivity nor has been exempted from validation. + */ + public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; + + /** + * The overall status of the network is that it is valid, this may be because it provides + * full Internet access (all probes succeeded), or because other properties of the network + * caused probes not to be run. + */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID + public static final int NETWORK_VALIDATION_RESULT_VALID = 1; + + /** + * The overall status of the network is that it provides partial connectivity; some + * probed services succeeded but others failed. + */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; + public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; + + /** + * Due to the properties of the network, validation was not performed. + */ + public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; + + /** @hide */ + @IntDef( + prefix = {"NETWORK_VALIDATION_RESULT_"}, + value = { + NETWORK_VALIDATION_RESULT_INVALID, + NETWORK_VALIDATION_RESULT_VALID, + NETWORK_VALIDATION_RESULT_PARTIALLY_VALID, + NETWORK_VALIDATION_RESULT_SKIPPED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkValidationResult {} + + /** + * The overall validation result for the Network being reported on. + * + *

The possible values for this key are: + * {@link #NETWORK_VALIDATION_RESULT_INVALID}, + * {@link #NETWORK_VALIDATION_RESULT_VALID}, + * {@link #NETWORK_VALIDATION_RESULT_PARTIALLY_VALID}, + * {@link #NETWORK_VALIDATION_RESULT_SKIPPED}. + * + * @see android.net.NetworkCapabilities#NET_CAPABILITY_VALIDATED + */ + @NetworkValidationResult + public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; + + /** DNS probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS + public static final int NETWORK_PROBE_DNS = 0x04; + + /** HTTP probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP + public static final int NETWORK_PROBE_HTTP = 0x08; + + /** HTTPS probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; + public static final int NETWORK_PROBE_HTTPS = 0x10; + + /** Captive portal fallback probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_FALLBACK + public static final int NETWORK_PROBE_FALLBACK = 0x20; + + /** Private DNS (DNS over TLS) probd. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS + public static final int NETWORK_PROBE_PRIVATE_DNS = 0x40; + + /** @hide */ + @IntDef( + prefix = {"NETWORK_PROBE_"}, + value = { + NETWORK_PROBE_DNS, + NETWORK_PROBE_HTTP, + NETWORK_PROBE_HTTPS, + NETWORK_PROBE_FALLBACK, + NETWORK_PROBE_PRIVATE_DNS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkProbe {} + + /** + * A bitmask of network validation probes that succeeded. + * + *

The possible bits values reported by this key are: + * {@link #NETWORK_PROBE_DNS}, + * {@link #NETWORK_PROBE_HTTP}, + * {@link #NETWORK_PROBE_HTTPS}, + * {@link #NETWORK_PROBE_FALLBACK}, + * {@link #NETWORK_PROBE_PRIVATE_DNS}. + */ + @NetworkProbe + public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = + "networkProbesSucceeded"; + + /** + * A bitmask of network validation probes that were attempted. + * + *

These probes may have failed or may be incomplete at the time of this report. + * + *

The possible bits values reported by this key are: + * {@link #NETWORK_PROBE_DNS}, + * {@link #NETWORK_PROBE_HTTP}, + * {@link #NETWORK_PROBE_HTTPS}, + * {@link #NETWORK_PROBE_FALLBACK}, + * {@link #NETWORK_PROBE_PRIVATE_DNS}. + */ + @NetworkProbe + public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = + "networkProbesAttempted"; + + /** @hide */ + @StringDef(prefix = {"KEY_"}, value = { + KEY_NETWORK_VALIDATION_RESULT, KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, + KEY_NETWORK_PROBES_ATTEMPTED_BITMASK}) + @Retention(RetentionPolicy.SOURCE) + public @interface ConnectivityReportBundleKeys {} + + /** The Network for which this ConnectivityReport applied */ + @NonNull private final Network mNetwork; + + /** + * The timestamp for the report. The timestamp is taken from {@link + * System#currentTimeMillis}. + */ + private final long mReportTimestamp; + + /** LinkProperties available on the Network at the reported timestamp */ + @NonNull private final LinkProperties mLinkProperties; + + /** NetworkCapabilities available on the Network at the reported timestamp */ + @NonNull private final NetworkCapabilities mNetworkCapabilities; + + /** PersistableBundle that may contain additional info about the report */ + @NonNull private final PersistableBundle mAdditionalInfo; + + /** + * Constructor for ConnectivityReport. + * + *

Apps should obtain instances through {@link + * ConnectivityDiagnosticsCallback#onConnectivityReportAvailable} instead of instantiating + * their own instances (unless for testing purposes). + * + * @param network The Network for which this ConnectivityReport applies + * @param reportTimestamp The timestamp for the report + * @param linkProperties The LinkProperties available on network at reportTimestamp + * @param networkCapabilities The NetworkCapabilities available on network at + * reportTimestamp + * @param additionalInfo A PersistableBundle that may contain additional info about the + * report + */ + public ConnectivityReport( + @NonNull Network network, + long reportTimestamp, + @NonNull LinkProperties linkProperties, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull PersistableBundle additionalInfo) { + mNetwork = network; + mReportTimestamp = reportTimestamp; + mLinkProperties = new LinkProperties(linkProperties); + mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); + mAdditionalInfo = additionalInfo; + } + + /** + * Returns the Network for this ConnectivityReport. + * + * @return The Network for which this ConnectivityReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the LinkProperties available when this report was taken. + * + * @return LinkProperties available on the Network at the reported timestamp + */ + @NonNull + public LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); + } + + /** + * Returns the NetworkCapabilities when this report was taken. + * + * @return NetworkCapabilities available on the Network at the reported timestamp + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return new NetworkCapabilities(mNetworkCapabilities); + } + + /** + * Returns a PersistableBundle with additional info for this report. + * + * @return PersistableBundle that may contain additional info about the report + */ + @NonNull + public PersistableBundle getAdditionalInfo() { + return new PersistableBundle(mAdditionalInfo); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof ConnectivityReport)) return false; + final ConnectivityReport that = (ConnectivityReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mNetwork.equals(that.mNetwork) + && mLinkProperties.equals(that.mLinkProperties) + && mNetworkCapabilities.equals(that.mNetworkCapabilities) + && persistableBundleEquals(mAdditionalInfo, that.mAdditionalInfo); + } + + @Override + public int hashCode() { + return Objects.hash( + mNetwork, + mReportTimestamp, + mLinkProperties, + mNetworkCapabilities, + mAdditionalInfo); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeParcelable(mLinkProperties, flags); + dest.writeParcelable(mNetworkCapabilities, flags); + dest.writeParcelable(mAdditionalInfo, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator() { + public ConnectivityReport createFromParcel(Parcel in) { + return new ConnectivityReport( + in.readParcelable(null), + in.readLong(), + in.readParcelable(null), + in.readParcelable(null), + in.readParcelable(null)); + } + + public ConnectivityReport[] newArray(int size) { + return new ConnectivityReport[size]; + } + }; + } + + /** Class that includes information for a suspected data stall on a specific Network */ + public static final class DataStallReport implements Parcelable { + /** + * Indicates that the Data Stall was detected using DNS events. + */ + public static final int DETECTION_METHOD_DNS_EVENTS = 1; + + /** + * Indicates that the Data Stall was detected using TCP metrics. + */ + public static final int DETECTION_METHOD_TCP_METRICS = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"DETECTION_METHOD_"}, + value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) + public @interface DetectionMethod {} + + /** + * This key represents the period in milliseconds over which other included TCP metrics + * were measured. + * + *

This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_TCP_METRICS}. + * + *

This value is an int. + */ + public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = + "tcpMetricsCollectionPeriodMillis"; + + /** + * This key represents the fail rate of TCP packets when the suspected data stall was + * detected. + * + *

This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_TCP_METRICS}. + * + *

This value is an int percentage between 0 and 100. + */ + public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; + + /** + * This key represents the consecutive number of DNS timeouts that have occurred. + * + *

The consecutive count will be reset any time a DNS response is received. + * + *

This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_DNS_EVENTS}. + * + *

This value is an int. + */ + public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = {"KEY_"}, value = { + KEY_TCP_PACKET_FAIL_RATE, + KEY_DNS_CONSECUTIVE_TIMEOUTS + }) + public @interface DataStallReportBundleKeys {} + + /** The Network for which this DataStallReport applied */ + @NonNull private final Network mNetwork; + + /** + * The timestamp for the report. The timestamp is taken from {@link + * System#currentTimeMillis}. + */ + private long mReportTimestamp; + + /** A bitmask of the detection methods used to identify the suspected data stall */ + @DetectionMethod private final int mDetectionMethod; + + /** LinkProperties available on the Network at the reported timestamp */ + @NonNull private final LinkProperties mLinkProperties; + + /** NetworkCapabilities available on the Network at the reported timestamp */ + @NonNull private final NetworkCapabilities mNetworkCapabilities; + + /** PersistableBundle that may contain additional information on the suspected data stall */ + @NonNull private final PersistableBundle mStallDetails; + + /** + * Constructor for DataStallReport. + * + *

Apps should obtain instances through {@link + * ConnectivityDiagnosticsCallback#onDataStallSuspected} instead of instantiating their own + * instances (unless for testing purposes). + * + * @param network The Network for which this DataStallReport applies + * @param reportTimestamp The timestamp for the report + * @param detectionMethod The detection method used to identify this data stall + * @param linkProperties The LinkProperties available on network at reportTimestamp + * @param networkCapabilities The NetworkCapabilities available on network at + * reportTimestamp + * @param stallDetails A PersistableBundle that may contain additional info about the report + */ + public DataStallReport( + @NonNull Network network, + long reportTimestamp, + @DetectionMethod int detectionMethod, + @NonNull LinkProperties linkProperties, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull PersistableBundle stallDetails) { + mNetwork = network; + mReportTimestamp = reportTimestamp; + mDetectionMethod = detectionMethod; + mLinkProperties = new LinkProperties(linkProperties); + mNetworkCapabilities = new NetworkCapabilities(networkCapabilities); + mStallDetails = stallDetails; + } + + /** + * Returns the Network for this DataStallReport. + * + * @return The Network for which this DataStallReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the bitmask of detection methods used to identify this suspected data stall. + * + * @return The bitmask of detection methods used to identify the suspected data stall + */ + public int getDetectionMethod() { + return mDetectionMethod; + } + + /** + * Returns the LinkProperties available when this report was taken. + * + * @return LinkProperties available on the Network at the reported timestamp + */ + @NonNull + public LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); + } + + /** + * Returns the NetworkCapabilities when this report was taken. + * + * @return NetworkCapabilities available on the Network at the reported timestamp + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return new NetworkCapabilities(mNetworkCapabilities); + } + + /** + * Returns a PersistableBundle with additional info for this report. + * + *

Gets a bundle with details about the suspected data stall including information + * specific to the monitoring method that detected the data stall. + * + * @return PersistableBundle that may contain additional information on the suspected data + * stall + */ + @NonNull + public PersistableBundle getStallDetails() { + return new PersistableBundle(mStallDetails); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof DataStallReport)) return false; + final DataStallReport that = (DataStallReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mDetectionMethod == that.mDetectionMethod + && mNetwork.equals(that.mNetwork) + && mLinkProperties.equals(that.mLinkProperties) + && mNetworkCapabilities.equals(that.mNetworkCapabilities) + && persistableBundleEquals(mStallDetails, that.mStallDetails); + } + + @Override + public int hashCode() { + return Objects.hash( + mNetwork, + mReportTimestamp, + mDetectionMethod, + mLinkProperties, + mNetworkCapabilities, + mStallDetails); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeInt(mDetectionMethod); + dest.writeParcelable(mLinkProperties, flags); + dest.writeParcelable(mNetworkCapabilities, flags); + dest.writeParcelable(mStallDetails, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator() { + public DataStallReport createFromParcel(Parcel in) { + return new DataStallReport( + in.readParcelable(null), + in.readLong(), + in.readInt(), + in.readParcelable(null), + in.readParcelable(null), + in.readParcelable(null)); + } + + public DataStallReport[] newArray(int size) { + return new DataStallReport[size]; + } + }; + } + + /** @hide */ + @VisibleForTesting + public static class ConnectivityDiagnosticsBinder + extends IConnectivityDiagnosticsCallback.Stub { + @NonNull private final ConnectivityDiagnosticsCallback mCb; + @NonNull private final Executor mExecutor; + + /** @hide */ + @VisibleForTesting + public ConnectivityDiagnosticsBinder( + @NonNull ConnectivityDiagnosticsCallback cb, @NonNull Executor executor) { + this.mCb = cb; + this.mExecutor = executor; + } + + /** @hide */ + @VisibleForTesting + public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCb.onConnectivityReportAvailable(report); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** @hide */ + @VisibleForTesting + public void onDataStallSuspected(@NonNull DataStallReport report) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCb.onDataStallSuspected(report); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** @hide */ + @VisibleForTesting + public void onNetworkConnectivityReported( + @NonNull Network network, boolean hasConnectivity) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mCb.onNetworkConnectivityReported(network, hasConnectivity); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + /** + * Abstract base class for Connectivity Diagnostics callbacks. Used for notifications about + * network connectivity events. Must be extended by applications wanting notifications. + */ + public abstract static class ConnectivityDiagnosticsCallback { + /** + * Called when the platform completes a data connectivity check. This will also be invoked + * immediately upon registration for each network matching the request with the latest + * report, if a report has already been generated for that network. + * + *

The Network specified in the ConnectivityReport may not be active any more when this + * method is invoked. + * + * @param report The ConnectivityReport containing information about a connectivity check + */ + public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) {} + + /** + * Called when the platform suspects a data stall on some Network. + * + *

The Network specified in the DataStallReport may not be active any more when this + * method is invoked. + * + * @param report The DataStallReport containing information about the suspected data stall + */ + public void onDataStallSuspected(@NonNull DataStallReport report) {} + + /** + * Called when any app reports connectivity to the System. + * + * @param network The Network for which connectivity has been reported + * @param hasConnectivity The connectivity reported to the System + */ + public void onNetworkConnectivityReported( + @NonNull Network network, boolean hasConnectivity) {} + } + + /** + * Registers a ConnectivityDiagnosticsCallback with the System. + * + *

Only apps that offer network connectivity to the user should be registering callbacks. + * These are the only apps whose callbacks will be invoked by the system. Apps considered to + * meet these conditions include: + * + *

    + *
  • Carrier apps with active subscriptions + *
  • Active VPNs + *
  • WiFi Suggesters + *
+ * + *

Callbacks registered by apps not meeting the above criteria will not be invoked. + * + *

If a registering app loses its relevant permissions, any callbacks it registered will + * silently stop receiving callbacks. + * + *

Each register() call MUST use a ConnectivityDiagnosticsCallback instance that is + * not currently registered. If a ConnectivityDiagnosticsCallback instance is registered with + * multiple NetworkRequests, an IllegalArgumentException will be thrown. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * callbacks in {@link ConnectivityManager}. Registering a callback with this method will count + * toward this limit. If this limit is exceeded, an exception will be thrown. To avoid hitting + * this issue and to conserve resources, make sure to unregister the callbacks with + * {@link #unregisterConnectivityDiagnosticsCallback}. + * + * @param request The NetworkRequest that will be used to match with Networks for which + * callbacks will be fired + * @param e The Executor to be used for running the callback method invocations + * @param callback The ConnectivityDiagnosticsCallback that the caller wants registered with the + * System + * @throws IllegalArgumentException if the same callback instance is registered with multiple + * NetworkRequests + * @throws RuntimeException if the app already has too many callbacks registered. + */ + public void registerConnectivityDiagnosticsCallback( + @NonNull NetworkRequest request, + @NonNull Executor e, + @NonNull ConnectivityDiagnosticsCallback callback) { + final ConnectivityDiagnosticsBinder binder = new ConnectivityDiagnosticsBinder(callback, e); + if (sCallbacks.putIfAbsent(callback, binder) != null) { + throw new IllegalArgumentException("Callback is currently registered"); + } + + try { + mService.registerConnectivityDiagnosticsCallback( + binder, request, mContext.getOpPackageName()); + } catch (RemoteException exception) { + exception.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a ConnectivityDiagnosticsCallback with the System. + * + *

If the given callback is not currently registered with the System, this operation will be + * a no-op. + * + * @param callback The ConnectivityDiagnosticsCallback to be unregistered from the System. + */ + public void unregisterConnectivityDiagnosticsCallback( + @NonNull ConnectivityDiagnosticsCallback callback) { + // unconditionally removing from sCallbacks prevents race conditions here, since remove() is + // atomic. + final ConnectivityDiagnosticsBinder binder = sCallbacks.remove(callback); + if (binder == null) return; + + try { + mService.unregisterConnectivityDiagnosticsCallback(binder); + } catch (RemoteException exception) { + exception.rethrowFromSystemServer(); + } + } +} diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java new file mode 100644 index 0000000000..7f07bba668 --- /dev/null +++ b/framework/src/android/net/ConnectivityManager.java @@ -0,0 +1,5058 @@ +/* + * Copyright (C) 2008 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; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; +import static android.net.NetworkRequest.Type.LISTEN; +import static android.net.NetworkRequest.Type.REQUEST; +import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.QosCallback.QosCallbackRegistrationException; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.PendingIntent; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.content.Intent; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.SocketKeepalive.Callback; +import android.net.TetheringManager.StartTetheringCallback; +import android.net.TetheringManager.TetheringEventCallback; +import android.net.TetheringManager.TetheringRequest; +import android.os.Binder; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.INetworkActivityListener; +import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.provider.Settings; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Range; +import android.util.SparseIntArray; + +import com.android.connectivity.aidl.INetworkAgent; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; +import com.android.internal.util.Protocol; + +import libcore.net.event.NetworkEventDispatcher; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; + +/** + * Class that answers queries about the state of network connectivity. It also + * notifies applications when network connectivity changes. + *

+ * The primary responsibilities of this class are to: + *

    + *
  1. Monitor network connections (Wi-Fi, GPRS, UMTS, etc.)
  2. + *
  3. Send broadcast intents when network connectivity changes
  4. + *
  5. Attempt to "fail over" to another network when connectivity to a network + * is lost
  6. + *
  7. Provide an API that allows applications to query the coarse-grained or fine-grained + * state of the available networks
  8. + *
  9. Provide an API that allows applications to request and select networks for their data + * traffic
  10. + *
+ */ +@SystemService(Context.CONNECTIVITY_SERVICE) +public class ConnectivityManager { + private static final String TAG = "ConnectivityManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + /** + * A change in network connectivity has occurred. A default connection has either + * been established or lost. The NetworkInfo for the affected network is + * sent as an extra; it should be consulted to see what kind of + * connectivity event occurred. + *

+ * Apps targeting Android 7.0 (API level 24) and higher do not receive this + * broadcast if they declare the broadcast receiver in their manifest. Apps + * will still receive broadcasts if they register their + * {@link android.content.BroadcastReceiver} with + * {@link android.content.Context#registerReceiver Context.registerReceiver()} + * and that context is still valid. + *

+ * If this is a connection that was the result of failing over from a + * disconnected network, then the FAILOVER_CONNECTION boolean extra is + * set to true. + *

+ * For a loss of connectivity, if the connectivity manager is attempting + * to connect (or has already connected) to another network, the + * NetworkInfo for the new network is also passed as an extra. This lets + * any receivers of the broadcast know that they should not necessarily + * tell the user that no data traffic will be possible. Instead, the + * receiver should expect another broadcast soon, indicating either that + * the failover attempt succeeded (and so there is still overall data + * connectivity), or that the failover attempt failed, meaning that all + * connectivity has been lost. + *

+ * For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY + * is set to {@code true} if there are no connected networks at all. + * + * @deprecated apps should use the more versatile {@link #requestNetwork}, + * {@link #registerNetworkCallback} or {@link #registerDefaultNetworkCallback} + * functions instead for faster and more detailed updates about the network + * changes they care about. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + + /** + * The device has connected to a network that has presented a captive + * portal, which is blocking Internet connectivity. The user was presented + * with a notification that network sign in is required, + * and the user invoked the notification's action indicating they + * desire to sign in to the network. Apps handling this activity should + * facilitate signing in to the network. This action includes a + * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents + * the network presenting the captive portal; all communication with the + * captive portal must be done using this {@code Network} object. + *

+ * This activity includes a {@link CaptivePortal} extra named + * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different + * outcomes of the captive portal sign in to the system: + *

    + *
  • When the app handling this action believes the user has signed in to + * the network and the captive portal has been dismissed, the app should + * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can + * reevaluate the network. If reevaluation finds the network no longer + * subject to a captive portal, the network may become the default active + * data network.
  • + *
  • When the app handling this action believes the user explicitly wants + * to ignore the captive portal and the network, the app should call + * {@link CaptivePortal#ignoreNetwork}.
  • + *
+ */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + + /** + * The lookup key for a {@link NetworkInfo} object. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + * + * @deprecated The {@link NetworkInfo} object is deprecated, as many of its properties + * can't accurately represent modern network characteristics. + * Please obtain information about networks from the {@link NetworkCapabilities} + * or {@link LinkProperties} objects instead. + */ + @Deprecated + public static final String EXTRA_NETWORK_INFO = "networkInfo"; + + /** + * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast. + * + * @see android.content.Intent#getIntExtra(String, int) + * @deprecated The network type is not rich enough to represent the characteristics + * of modern networks. Please use {@link NetworkCapabilities} instead, + * in particular the transports. + */ + @Deprecated + public static final String EXTRA_NETWORK_TYPE = "networkType"; + + /** + * The lookup key for a boolean that indicates whether a connect event + * is for a network to which the connectivity manager was failing over + * following a disconnect on another network. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public static final String EXTRA_IS_FAILOVER = "isFailover"; + /** + * The lookup key for a {@link NetworkInfo} object. This is supplied when + * there is another network that it may be possible to connect to. Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; + /** + * The lookup key for a boolean that indicates whether there is a + * complete lack of connectivity, i.e., no network is available. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + */ + public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; + /** + * The lookup key for a string that indicates why an attempt to connect + * to a network failed. The string has no particular structure. It is + * intended to be used in notifications presented to users. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_REASON = "reason"; + /** + * The lookup key for a string that provides optionally supplied + * extra information about the network state. The information + * may be passed up from the lower networking layers, and its + * meaning may be specific to a particular network type. Retrieve + * it with {@link android.content.Intent#getStringExtra(String)}. + * + * @deprecated See {@link NetworkInfo#getExtraInfo()}. + */ + @Deprecated + public static final String EXTRA_EXTRA_INFO = "extraInfo"; + /** + * The lookup key for an int that provides information about + * our connection to the internet at large. 0 indicates no connection, + * 100 indicates a great connection. Retrieve it with + * {@link android.content.Intent#getIntExtra(String, int)}. + * {@hide} + */ + public static final String EXTRA_INET_CONDITION = "inetCondition"; + /** + * The lookup key for a {@link CaptivePortal} object included with the + * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent. The {@code CaptivePortal} + * object can be used to either indicate to the system that the captive + * portal has been dismissed or that the user does not want to pursue + * signing in to captive portal. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; + + /** + * Key for passing a URL to the captive portal login activity. + */ + public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; + + /** + * Key for passing a {@link android.net.captiveportal.CaptivePortalProbeSpec} to the captive + * portal login activity. + * {@hide} + */ + @SystemApi + public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = + "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; + + /** + * Key for passing a user agent string to the captive portal login activity. + * {@hide} + */ + @SystemApi + public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = + "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + + /** + * Broadcast action to indicate the change of data activity status + * (idle or active) on a network in a recent period. + * The network becomes active when data transmission is started, or + * idle if there is no data transmission for a period of time. + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DATA_ACTIVITY_CHANGE = + "android.net.conn.DATA_ACTIVITY_CHANGE"; + /** + * The lookup key for an enum that indicates the network device type on which this data activity + * change happens. + * {@hide} + */ + public static final String EXTRA_DEVICE_TYPE = "deviceType"; + /** + * The lookup key for a boolean that indicates the device is active or not. {@code true} means + * it is actively sending or receiving data and {@code false} means it is idle. + * {@hide} + */ + public static final String EXTRA_IS_ACTIVE = "isActive"; + /** + * The lookup key for a long that contains the timestamp (nanos) of the radio state change. + * {@hide} + */ + public static final String EXTRA_REALTIME_NS = "tsNanos"; + + /** + * Broadcast Action: The setting for background data usage has changed + * values. Use {@link #getBackgroundDataSetting()} to get the current value. + *

+ * If an application uses the network in the background, it should listen + * for this broadcast and stop using the background data if the value is + * {@code false}. + *

+ * + * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability + * of background data depends on several combined factors, and + * this broadcast is no longer sent. Instead, when background + * data is unavailable, {@link #getActiveNetworkInfo()} will now + * appear disconnected. During first boot after a platform + * upgrade, this broadcast will be sent once if + * {@link #getBackgroundDataSetting()} was {@code false} before + * the upgrade. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = + "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + + /** + * Broadcast Action: The network connection may not be good + * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and + * {@code ConnectivityManager.EXTRA_NETWORK_INFO} to specify + * the network and it's condition. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage + public static final String INET_CONDITION_ACTION = + "android.net.conn.INET_CONDITION_ACTION"; + + /** + * Broadcast Action: A tetherable connection has come or gone. + * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER}, + * {@code ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY}, + * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER}, and + * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate + * the current state of tethering. Each include a list of + * interface names in that state (may be empty). + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String ACTION_TETHER_STATE_CHANGED = + TetheringManager.ACTION_TETHER_STATE_CHANGED; + + /** + * @hide + * gives a String[] listing all the interfaces configured for + * tethering and currently available for tethering. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_AVAILABLE_TETHER = TetheringManager.EXTRA_AVAILABLE_TETHER; + + /** + * @hide + * gives a String[] listing all the interfaces currently in local-only + * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) + */ + public static final String EXTRA_ACTIVE_LOCAL_ONLY = TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY; + + /** + * @hide + * gives a String[] listing all the interfaces currently tethered + * (ie, has DHCPv4 support and packets potentially forwarded/NATed) + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_ACTIVE_TETHER = TetheringManager.EXTRA_ACTIVE_TETHER; + + /** + * @hide + * gives a String[] listing all the interfaces we tried to tether and + * failed. Use {@link #getLastTetherError} to find the error code + * for any interfaces listed here. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final String EXTRA_ERRORED_TETHER = TetheringManager.EXTRA_ERRORED_TETHER; + + /** + * Broadcast Action: The captive portal tracker has finished its test. + * Sent only while running Setup Wizard, in lieu of showing a user + * notification. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CAPTIVE_PORTAL_TEST_COMPLETED = + "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED"; + /** + * The lookup key for a boolean that indicates whether a captive portal was detected. + * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * @hide + */ + public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal"; + + /** + * Action used to display a dialog that asks the user whether to connect to a network that is + * not validated. This intent is used to start the dialog in settings via startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED"; + + /** + * Action used to display a dialog that asks the user whether to avoid a network that is no + * longer validated. This intent is used to start the dialog in settings via startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_LOST_VALIDATION = + "android.net.conn.PROMPT_LOST_VALIDATION"; + + /** + * Action used to display a dialog that asks the user whether to stay connected to a network + * that has not validated. This intent is used to start the dialog in settings via + * startActivity. + * + * @hide + */ + public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = + "android.net.conn.PROMPT_PARTIAL_CONNECTIVITY"; + + /** + * Invalid tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + public static final int TETHERING_INVALID = TetheringManager.TETHERING_INVALID; + + /** + * Wifi tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_WIFI = TetheringManager.TETHERING_WIFI; + + /** + * USB tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_USB = TetheringManager.TETHERING_USB; + + /** + * Bluetooth tethering type. + * @see #startTethering(int, boolean, OnStartTetheringCallback) + * @hide + */ + @SystemApi + public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH; + + /** + * Wifi P2p tethering type. + * Wifi P2p tethering is set through events automatically, and don't + * need to start from #startTethering(int, boolean, OnStartTetheringCallback). + * @hide + */ + public static final int TETHERING_WIFI_P2P = TetheringManager.TETHERING_WIFI_P2P; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering to + * enable if any. + * @hide + */ + public static final String EXTRA_ADD_TETHER_TYPE = TetheringConstants.EXTRA_ADD_TETHER_TYPE; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering for + * which to cancel provisioning. + * @hide + */ + public static final String EXTRA_REM_TETHER_TYPE = TetheringConstants.EXTRA_REM_TETHER_TYPE; + + /** + * Extra used for communicating with the TetherService. True to schedule a recheck of tether + * provisioning. + * @hide + */ + public static final String EXTRA_SET_ALARM = TetheringConstants.EXTRA_SET_ALARM; + + /** + * Tells the TetherService to run a provision check now. + * @hide + */ + public static final String EXTRA_RUN_PROVISION = TetheringConstants.EXTRA_RUN_PROVISION; + + /** + * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} + * which will receive provisioning results. Can be left empty. + * @hide + */ + public static final String EXTRA_PROVISION_CALLBACK = + TetheringConstants.EXTRA_PROVISION_CALLBACK; + + /** + * The absence of a connection type. + * @hide + */ + @SystemApi + public static final int TYPE_NONE = -1; + + /** + * A Mobile data connection. Devices may support more than one. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_MOBILE = 0; + + /** + * A WIFI data connection. Devices may support more than one. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_WIFI = 1; + + /** + * An MMS-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is used by applications needing to talk to the carrier's + * Multimedia Messaging Service servers. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_MMS = 2; + + /** + * A SUPL-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is used by applications needing to talk to the carrier's + * Secure User Plane Location servers for help locating the device. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_SUPL = 3; + + /** + * A DUN-specific Mobile data connection. This network type may use the + * same network interface as {@link #TYPE_MOBILE} or it may use a different + * one. This is sometimes by the system when setting up an upstream connection + * for tethering so that the carrier is aware of DUN traffic. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasCapability} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that + * provides the {@link NetworkCapabilities#NET_CAPABILITY_DUN} capability. + */ + @Deprecated + public static final int TYPE_MOBILE_DUN = 4; + + /** + * A High Priority Mobile data connection. This network type uses the + * same network interface as {@link #TYPE_MOBILE} but the routing setup + * is different. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_MOBILE_HIPRI = 5; + + /** + * A WiMAX data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_WIMAX = 6; + + /** + * A Bluetooth data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_BLUETOOTH = 7; + + /** + * Fake data connection. This should not be used on shipping devices. + * @deprecated This is not used any more. + */ + @Deprecated + public static final int TYPE_DUMMY = 8; + + /** + * An Ethernet data connection. + * + * @deprecated Applications should instead use {@link NetworkCapabilities#hasTransport} or + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request an + * appropriate network. {@see NetworkCapabilities} for supported transports. + */ + @Deprecated + public static final int TYPE_ETHERNET = 9; + + /** + * Over the air Administration. + * @deprecated Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_FOTA = 10; + + /** + * IP Multimedia Subsystem. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IMS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage + public static final int TYPE_MOBILE_IMS = 11; + + /** + * Carrier Branded Services. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_CBS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_CBS = 12; + + /** + * A Wi-Fi p2p connection. Only requesting processes will have access to + * the peers connected. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_WIFI_P2P} instead. + * {@hide} + */ + @Deprecated + @SystemApi + public static final int TYPE_WIFI_P2P = 13; + + /** + * The network to use for initially attaching to the network + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_IA} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage + public static final int TYPE_MOBILE_IA = 14; + + /** + * Emergency PDN connection for emergency services. This + * may include IMS and MMS in emergency situations. + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_EIMS} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static final int TYPE_MOBILE_EMERGENCY = 15; + + /** + * The network that uses proxy to achieve connectivity. + * @deprecated Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @SystemApi + public static final int TYPE_PROXY = 16; + + /** + * A virtual network using one or more native bearers. + * It may or may not be providing security services. + * @deprecated Applications should use {@link NetworkCapabilities#TRANSPORT_VPN} instead. + */ + @Deprecated + public static final int TYPE_VPN = 17; + + /** + * A network that is exclusively meant to be used for testing + * + * @deprecated Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public static final int TYPE_TEST = 18; // TODO: Remove this once NetworkTypes are unused. + + /** + * @deprecated Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_NONE, + TYPE_MOBILE, + TYPE_WIFI, + TYPE_MOBILE_MMS, + TYPE_MOBILE_SUPL, + TYPE_MOBILE_DUN, + TYPE_MOBILE_HIPRI, + TYPE_WIMAX, + TYPE_BLUETOOTH, + TYPE_DUMMY, + TYPE_ETHERNET, + TYPE_MOBILE_FOTA, + TYPE_MOBILE_IMS, + TYPE_MOBILE_CBS, + TYPE_WIFI_P2P, + TYPE_MOBILE_IA, + TYPE_MOBILE_EMERGENCY, + TYPE_PROXY, + TYPE_VPN, + TYPE_TEST + }) + public @interface LegacyNetworkType {} + + // Deprecated constants for return values of startUsingNetworkFeature. They used to live + // in com.android.internal.telephony.PhoneConstants until they were made inaccessible. + private static final int DEPRECATED_PHONE_CONSTANT_APN_ALREADY_ACTIVE = 0; + private static final int DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED = 1; + private static final int DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED = 3; + + /** {@hide} */ + public static final int MAX_RADIO_TYPE = TYPE_TEST; + + /** {@hide} */ + public static final int MAX_NETWORK_TYPE = TYPE_TEST; + + private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; + + /** + * If you want to set the default network preference,you can directly + * change the networkAttributes array in framework's config.xml. + * + * @deprecated Since we support so many more networks now, the single + * network default network preference can't really express + * the hierarchy. Instead, the default is defined by the + * networkAttributes in config.xml. You can determine + * the current value by calling {@link #getNetworkPreference()} + * from an App. + */ + @Deprecated + public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI; + + /** + * @hide + */ + public static final int REQUEST_ID_UNSET = 0; + + /** + * Static unique request used as a tombstone for NetworkCallbacks that have been unregistered. + * This allows to distinguish when unregistering NetworkCallbacks those that were never + * registered from those that were already unregistered. + * @hide + */ + private static final NetworkRequest ALREADY_UNREGISTERED = + new NetworkRequest.Builder().clearCapabilities().build(); + + /** + * A NetID indicating no Network is selected. + * Keep in sync with bionic/libc/dns/include/resolv_netid.h + * @hide + */ + public static final int NETID_UNSET = 0; + + /** + * Private DNS Mode values. + * + * The "private_dns_mode" global setting stores a String value which is + * expected to be one of the following. + */ + + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_OFF = "off"; + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; + /** + * @hide + */ + public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; + /** + * The default Private DNS mode. + * + * This may change from release to release or may become dependent upon + * the capabilities of the underlying platform. + * + * @hide + */ + public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC; + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + private final IConnectivityManager mService; + /** + * A kludge to facilitate static access where a Context pointer isn't available, like in the + * case of the static set/getProcessDefaultNetwork methods and from the Network class. + * TODO: Remove this after deprecating the static methods in favor of non-static methods or + * methods that take a Context argument. + */ + private static ConnectivityManager sInstance; + + private final Context mContext; + + private INetworkManagementService mNMService; + private INetworkPolicyManager mNPManager; + private final TetheringManager mTetheringManager; + + /** + * Tests if a given integer represents a valid network type. + * @param networkType the type to be tested + * @return a boolean. {@code true} if the type is valid, else {@code false} + * @deprecated All APIs accepting a network type are deprecated. There should be no need to + * validate a network type. + */ + @Deprecated + public static boolean isNetworkTypeValid(int networkType) { + return MIN_NETWORK_TYPE <= networkType && networkType <= MAX_NETWORK_TYPE; + } + + /** + * Returns a non-localized string representing a given network type. + * ONLY used for debugging output. + * @param type the type needing naming + * @return a String for the given type, or a string version of the type ("87") + * if no name is known. + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static String getNetworkTypeName(int type) { + switch (type) { + case TYPE_NONE: + return "NONE"; + case TYPE_MOBILE: + return "MOBILE"; + case TYPE_WIFI: + return "WIFI"; + case TYPE_MOBILE_MMS: + return "MOBILE_MMS"; + case TYPE_MOBILE_SUPL: + return "MOBILE_SUPL"; + case TYPE_MOBILE_DUN: + return "MOBILE_DUN"; + case TYPE_MOBILE_HIPRI: + return "MOBILE_HIPRI"; + case TYPE_WIMAX: + return "WIMAX"; + case TYPE_BLUETOOTH: + return "BLUETOOTH"; + case TYPE_DUMMY: + return "DUMMY"; + case TYPE_ETHERNET: + return "ETHERNET"; + case TYPE_MOBILE_FOTA: + return "MOBILE_FOTA"; + case TYPE_MOBILE_IMS: + return "MOBILE_IMS"; + case TYPE_MOBILE_CBS: + return "MOBILE_CBS"; + case TYPE_WIFI_P2P: + return "WIFI_P2P"; + case TYPE_MOBILE_IA: + return "MOBILE_IA"; + case TYPE_MOBILE_EMERGENCY: + return "MOBILE_EMERGENCY"; + case TYPE_PROXY: + return "PROXY"; + case TYPE_VPN: + return "VPN"; + default: + return Integer.toString(type); + } + } + + /** + * @hide + * TODO: Expose for SystemServer when becomes a module. + */ + public void systemReady() { + try { + mService.systemReady(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Checks if a given type uses the cellular data connection. + * This should be replaced in the future by a network property. + * @param networkType the type to check + * @return a boolean - {@code true} if uses cellular network, else {@code false} + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * {@hide} + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public static boolean isNetworkTypeMobile(int networkType) { + switch (networkType) { + case TYPE_MOBILE: + case TYPE_MOBILE_MMS: + case TYPE_MOBILE_SUPL: + case TYPE_MOBILE_DUN: + case TYPE_MOBILE_HIPRI: + case TYPE_MOBILE_FOTA: + case TYPE_MOBILE_IMS: + case TYPE_MOBILE_CBS: + case TYPE_MOBILE_IA: + case TYPE_MOBILE_EMERGENCY: + return true; + default: + return false; + } + } + + /** + * Checks if the given network type is backed by a Wi-Fi radio. + * + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + public static boolean isNetworkTypeWifi(int networkType) { + switch (networkType) { + case TYPE_WIFI: + case TYPE_WIFI_P2P: + return true; + default: + return false; + } + } + + /** + * Specifies the preferred network type. When the device has more + * than one type available the preferred network type will be used. + * + * @param preference the network type to prefer over all others. It is + * unspecified what happens to the old preferred network in the + * overall ordering. + * @deprecated Functionality has been removed as it no longer makes sense, + * with many more than two networks - we'd need an array to express + * preference. Instead we use dynamic network properties of + * the networks to describe their precedence. + */ + @Deprecated + public void setNetworkPreference(int preference) { + } + + /** + * Retrieves the current preferred network type. + * + * @return an integer representing the preferred network type + * + * @deprecated Functionality has been removed as it no longer makes sense, + * with many more than two networks - we'd need an array to express + * preference. Instead we use dynamic network properties of + * the networks to describe their precedence. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public int getNetworkPreference() { + return TYPE_NONE; + } + + /** + * Returns details about the currently active default data network. When + * connected, this network is the default route for outgoing connections. + * You should always check {@link NetworkInfo#isConnected()} before initiating + * network traffic. This may return {@code null} when there is no default + * network. + * Note that if the default network is a VPN, this method will return the + * NetworkInfo for one of its underlying networks instead, or null if the + * VPN agent did not specify any. Apps interested in learning about VPNs + * should use {@link #getNetworkInfo(android.net.Network)} instead. + * + * @return a {@link NetworkInfo} object for the current default network + * or {@code null} if no default network is currently active + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getActiveNetworkInfo() { + try { + return mService.getActiveNetworkInfo(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a {@link Network} object corresponding to the currently active + * default data network. In the event that the current active default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network. + * + * @return a {@link Network} object for the current default network or + * {@code null} if no default network is currently active + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public Network getActiveNetwork() { + try { + return mService.getActiveNetwork(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a {@link Network} object corresponding to the currently active + * default data network for a specific UID. In the event that the default data + * network disconnects, the returned {@code Network} object will no longer + * be usable. This will return {@code null} when there is no default + * network for the UID. + * + * @return a {@link Network} object for the current default network for the + * given UID or {@code null} if no default network is currently active + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + @Nullable + public Network getActiveNetworkForUid(int uid) { + return getActiveNetworkForUid(uid, false); + } + + /** {@hide} */ + public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) { + try { + return mService.getActiveNetworkForUid(uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Checks if a VPN app supports always-on mode. + * + * In order to support the always-on feature, an app has to + *

    + *
  • target {@link VERSION_CODES#N API 24} or above, and + *
  • not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} + * meta-data field. + *
+ * + * @param userId The identifier of the user for whom the VPN app is installed. + * @param vpnPackage The canonical package name of the VPN app. + * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * @hide + */ + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + try { + return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + *

The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @param lockdownAllowlist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be allowed is installed or + * uninstalled. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List lockdownAllowlist) { + try { + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownAllowlist); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the package name of the currently set always-on VPN application. + * If there is no always-on VPN set, or the VPN is provided by the system instead + * of by an app, {@code null} will be returned. + * + * @return Package name of VPN controller responsible for always-on VPN, + * or {@code null} if none is set. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public String getAlwaysOnVpnPackageForUser(int userId) { + try { + return mService.getAlwaysOnVpnPackage(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List getVpnLockdownWhitelist(int userId) { + try { + return mService.getVpnLockdownWhitelist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds or removes a requirement for given UID ranges to use the VPN. + * + * If set to {@code true}, informs the system that the UIDs in the specified ranges must not + * have any connectivity except if a VPN is connected and applies to the UIDs, or if the UIDs + * otherwise have permission to bypass the VPN (e.g., because they have the + * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission, or when + * using a socket protected by a method such as {@link VpnService#protect(DatagramSocket)}. If + * set to {@code false}, a previously-added restriction is removed. + *

+ * Each of the UID ranges specified by this method is added and removed as is, and no processing + * is performed on the ranges to de-duplicate, merge, split, or intersect them. In order to + * remove a previously-added range, the exact range must be removed as is. + *

+ * The changes are applied asynchronously and may not have been applied by the time the method + * returns. Apps will be notified about any changes that apply to them via + * {@link NetworkCallback#onBlockedStatusChanged} callbacks called after the changes take + * effect. + *

+ * This method should be called only by the VPN code. + * + * @param ranges the UID ranges to restrict + * @param requireVpn whether the specified UID ranges must use a VPN + * + * TODO: expose as @SystemApi. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void setRequireVpnForUids(boolean requireVpn, + @NonNull Collection> ranges) { + Objects.requireNonNull(ranges); + // The Range class is not parcelable. Convert to UidRange, which is what is used internally. + // This method is not necessarily expected to be used outside the system server, so + // parceling may not be necessary, but it could be used out-of-process, e.g., by the network + // stack process, or by tests. + UidRange[] rangesArray = new UidRange[ranges.size()]; + int index = 0; + for (Range range : ranges) { + rangesArray[index++] = new UidRange(range.getLower(), range.getUpper()); + } + try { + mService.setRequireVpnForUids(requireVpn, rangesArray); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns details about the currently active default data network + * for a given uid. This is for internal use only to avoid spying + * other apps. + * + * @return a {@link NetworkInfo} object for the current default network + * for the given uid or {@code null} if no default network is + * available for the specified uid. + * + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public NetworkInfo getActiveNetworkInfoForUid(int uid) { + return getActiveNetworkInfoForUid(uid, false); + } + + /** {@hide} */ + public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { + try { + return mService.getActiveNetworkInfoForUid(uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about a particular + * network type. + * + * @param networkType integer specifying which networkType in + * which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network type or {@code null} if the type is not + * supported by the device. If {@code networkType} is + * TYPE_VPN and a VPN is active for the calling app, + * then this method will try to return one of the + * underlying networks for the VPN or null if the + * VPN agent didn't specify any. + * + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getNetworkInfo(int networkType) { + try { + return mService.getNetworkInfo(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about a particular + * Network. + * + * @param network {@link Network} specifying which network + * in which you're interested. + * @return a {@link NetworkInfo} object for the requested + * network or {@code null} if the {@code Network} + * is not valid. + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkInfo getNetworkInfo(@Nullable Network network) { + return getNetworkInfoForUid(network, Process.myUid(), false); + } + + /** {@hide} */ + public NetworkInfo getNetworkInfoForUid(Network network, int uid, boolean ignoreBlocked) { + try { + return mService.getNetworkInfoForUid(network, uid, ignoreBlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns connection status information about all network + * types supported by the device. + * + * @return an array of {@link NetworkInfo} objects. Check each + * {@link NetworkInfo#getType} for which type each applies. + * + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @NonNull + public NetworkInfo[] getAllNetworkInfo() { + try { + return mService.getAllNetworkInfo(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the {@link Network} object currently serving a given type, or + * null if the given type is not connected. + * + * @hide + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks} and + * {@link #getNetworkInfo(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + public Network getNetworkForType(int networkType) { + try { + return mService.getNetworkForType(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns an array of all {@link Network} currently tracked by the + * framework. + * + * @return an array of {@link Network} objects. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @NonNull + public Network[] getAllNetworks() { + try { + return mService.getAllNetworks(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns an array of {@link android.net.NetworkCapabilities} objects, representing + * the Networks that applications run by the given user will use by default. + * @hide + */ + @UnsupportedAppUsage + public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) { + try { + return mService.getDefaultNetworkCapabilitiesForUser( + userId, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the IP information for the current default network. + * + * @return a {@link LinkProperties} object describing the IP info + * for the current default network, or {@code null} if there + * is no current default network. + * + * {@hide} + * @deprecated please use {@link #getLinkProperties(Network)} on the return + * value of {@link #getActiveNetwork()} instead. In particular, + * this method will return non-null LinkProperties even if the + * app is blocked by policy from using this network. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 109783091) + public LinkProperties getActiveLinkProperties() { + try { + return mService.getActiveLinkProperties(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the IP information for a given network type. + * + * @param networkType the network type of interest. + * @return a {@link LinkProperties} object describing the IP info + * for the given networkType, or {@code null} if there is + * no current default network. + * + * {@hide} + * @deprecated This method does not support multiple connected networks + * of the same type. Use {@link #getAllNetworks}, + * {@link #getNetworkInfo(android.net.Network)}, and + * {@link #getLinkProperties(android.net.Network)} instead. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public LinkProperties getLinkProperties(int networkType) { + try { + return mService.getLinkPropertiesForType(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the {@link LinkProperties} for the given {@link Network}. This + * will return {@code null} if the network is unknown. + * + * @param network The {@link Network} object identifying the network in question. + * @return The {@link LinkProperties} for the network, or {@code null}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public LinkProperties getLinkProperties(@Nullable Network network) { + try { + return mService.getLinkProperties(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This + * will return {@code null} if the network is unknown. + * + * @param network The {@link Network} object identifying the network in question. + * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @Nullable + public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) { + try { + return mService.getNetworkCapabilities(network, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets a URL that can be used for resolving whether a captive portal is present. + * 1. This URL should respond with a 204 response to a GET request to indicate no captive + * portal is present. + * 2. This URL must be HTTP as redirect responses are used to find captive portal + * sign-in pages. Captive portals cannot respond to HTTPS requests with redirects. + * + * The system network validation may be using different strategies to detect captive portals, + * so this method does not necessarily return a URL used by the system. It only returns a URL + * that may be relevant for other components trying to detect captive portals. + * + * @hide + * @deprecated This API returns URL which is not guaranteed to be one of the URLs used by the + * system. + */ + @Deprecated + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public String getCaptivePortalServerUrl() { + try { + return mService.getCaptivePortalServerUrl(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Tells the underlying networking system that the caller wants to + * begin using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature to be used + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + * + * @deprecated Deprecated in favor of the cleaner + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public int startUsingNetworkFeature(int networkType, String feature) { + checkLegacyRoutingApiAccess(); + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " + + feature); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED; + } + + NetworkRequest request = null; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) { + Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest); + renewRequestLocked(l); + if (l.currentNetwork != null) { + return DEPRECATED_PHONE_CONSTANT_APN_ALREADY_ACTIVE; + } else { + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED; + } + } + + request = requestNetworkForFeatureLocked(netCap); + } + if (request != null) { + Log.d(TAG, "starting startUsingNetworkFeature for request " + request); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_STARTED; + } else { + Log.d(TAG, " request Failed"); + return DEPRECATED_PHONE_CONSTANT_APN_REQUEST_FAILED; + } + } + + /** + * Tells the underlying networking system that the caller is finished + * using the named feature. The interpretation of {@code feature} + * is completely up to each networking implementation. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType specifies which network the request pertains to + * @param feature the name of the feature that is no longer needed + * @return an integer value representing the outcome of the request. + * The interpretation of this value is specific to each networking + * implementation+feature combination, except that the value {@code -1} + * always indicates failure. + * + * @deprecated Deprecated in favor of the cleaner + * {@link #unregisterNetworkCallback(NetworkCallback)} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public int stopUsingNetworkFeature(int networkType, String feature) { + checkLegacyRoutingApiAccess(); + NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature); + if (netCap == null) { + Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " + + feature); + return -1; + } + + if (removeRequestForFeature(netCap)) { + Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature); + } + return 1; + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) { + if (networkType == TYPE_MOBILE) { + switch (feature) { + case "enableCBS": + return networkCapabilitiesForType(TYPE_MOBILE_CBS); + case "enableDUN": + case "enableDUNAlways": + return networkCapabilitiesForType(TYPE_MOBILE_DUN); + case "enableFOTA": + return networkCapabilitiesForType(TYPE_MOBILE_FOTA); + case "enableHIPRI": + return networkCapabilitiesForType(TYPE_MOBILE_HIPRI); + case "enableIMS": + return networkCapabilitiesForType(TYPE_MOBILE_IMS); + case "enableMMS": + return networkCapabilitiesForType(TYPE_MOBILE_MMS); + case "enableSUPL": + return networkCapabilitiesForType(TYPE_MOBILE_SUPL); + default: + return null; + } + } else if (networkType == TYPE_WIFI && "p2p".equals(feature)) { + return networkCapabilitiesForType(TYPE_WIFI_P2P); + } + return null; + } + + private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) { + if (netCap == null) return TYPE_NONE; + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) { + return TYPE_MOBILE_CBS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) { + return TYPE_MOBILE_IMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) { + return TYPE_MOBILE_FOTA; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) { + return TYPE_MOBILE_DUN; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) { + return TYPE_MOBILE_SUPL; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) { + return TYPE_MOBILE_MMS; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + return TYPE_MOBILE_HIPRI; + } + if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) { + return TYPE_WIFI_P2P; + } + return TYPE_NONE; + } + + private static class LegacyRequest { + NetworkCapabilities networkCapabilities; + NetworkRequest networkRequest; + int expireSequenceNumber; + Network currentNetwork; + int delay = -1; + + private void clearDnsBinding() { + if (currentNetwork != null) { + currentNetwork = null; + setProcessDefaultNetworkForHostResolution(null); + } + } + + NetworkCallback networkCallback = new NetworkCallback() { + @Override + public void onAvailable(Network network) { + currentNetwork = network; + Log.d(TAG, "startUsingNetworkFeature got Network:" + network); + setProcessDefaultNetworkForHostResolution(network); + } + @Override + public void onLost(Network network) { + if (network.equals(currentNetwork)) clearDnsBinding(); + Log.d(TAG, "startUsingNetworkFeature lost Network:" + network); + } + }; + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private static final HashMap sLegacyRequests = + new HashMap<>(); + + private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) { + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l != null) return l.networkRequest; + } + return null; + } + + private void renewRequestLocked(LegacyRequest l) { + l.expireSequenceNumber++; + Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber); + sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay); + } + + private void expireRequest(NetworkCapabilities netCap, int sequenceNum) { + int ourSeqNum = -1; + synchronized (sLegacyRequests) { + LegacyRequest l = sLegacyRequests.get(netCap); + if (l == null) return; + ourSeqNum = l.expireSequenceNumber; + if (l.expireSequenceNumber == sequenceNum) removeRequestForFeature(netCap); + } + Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum); + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) { + int delay = -1; + int type = legacyTypeForNetworkCapabilities(netCap); + try { + delay = mService.getRestoreDefaultNetworkDelay(type); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + LegacyRequest l = new LegacyRequest(); + l.networkCapabilities = netCap; + l.delay = delay; + l.expireSequenceNumber = 0; + l.networkRequest = sendRequestForNetwork( + netCap, l.networkCallback, 0, REQUEST, type, getDefaultHandler()); + if (l.networkRequest == null) return null; + sLegacyRequests.put(netCap, l); + sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay); + return l.networkRequest; + } + + private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) { + if (delay >= 0) { + Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay); + CallbackHandler handler = getDefaultHandler(); + Message msg = handler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap); + handler.sendMessageDelayed(msg, delay); + } + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private boolean removeRequestForFeature(NetworkCapabilities netCap) { + final LegacyRequest l; + synchronized (sLegacyRequests) { + l = sLegacyRequests.remove(netCap); + } + if (l == null) return false; + unregisterNetworkCallback(l.networkCallback); + l.clearDnsBinding(); + return true; + } + + private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray(); + static { + sLegacyTypeToTransport.put(TYPE_MOBILE, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_CBS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_DUN, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_FOTA, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_IMS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_MMS, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_SUPL, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_WIFI, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_WIFI_P2P, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_BLUETOOTH, NetworkCapabilities.TRANSPORT_BLUETOOTH); + sLegacyTypeToTransport.put(TYPE_ETHERNET, NetworkCapabilities.TRANSPORT_ETHERNET); + } + + private static final SparseIntArray sLegacyTypeToCapability = new SparseIntArray(); + static { + sLegacyTypeToCapability.put(TYPE_MOBILE_CBS, NetworkCapabilities.NET_CAPABILITY_CBS); + sLegacyTypeToCapability.put(TYPE_MOBILE_DUN, NetworkCapabilities.NET_CAPABILITY_DUN); + sLegacyTypeToCapability.put(TYPE_MOBILE_FOTA, NetworkCapabilities.NET_CAPABILITY_FOTA); + sLegacyTypeToCapability.put(TYPE_MOBILE_IMS, NetworkCapabilities.NET_CAPABILITY_IMS); + sLegacyTypeToCapability.put(TYPE_MOBILE_MMS, NetworkCapabilities.NET_CAPABILITY_MMS); + sLegacyTypeToCapability.put(TYPE_MOBILE_SUPL, NetworkCapabilities.NET_CAPABILITY_SUPL); + sLegacyTypeToCapability.put(TYPE_WIFI_P2P, NetworkCapabilities.NET_CAPABILITY_WIFI_P2P); + } + + /** + * Given a legacy type (TYPE_WIFI, ...) returns a NetworkCapabilities + * instance suitable for registering a request or callback. Throws an + * IllegalArgumentException if no mapping from the legacy type to + * NetworkCapabilities is known. + * + * @deprecated Types are deprecated. Use {@link NetworkCallback} or {@link NetworkRequest} + * to find the network instead. + * @hide + */ + public static NetworkCapabilities networkCapabilitiesForType(int type) { + final NetworkCapabilities nc = new NetworkCapabilities(); + + // Map from type to transports. + final int NOT_FOUND = -1; + final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND); + Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type); + nc.addTransportType(transport); + + // Map from type to capabilities. + nc.addCapability(sLegacyTypeToCapability.get( + type, NetworkCapabilities.NET_CAPABILITY_INTERNET)); + nc.maybeMarkCapabilitiesRestricted(); + return nc; + } + + /** @hide */ + public static class PacketKeepaliveCallback { + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public PacketKeepaliveCallback() { + } + /** The requested keepalive was successfully started. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onStarted() {} + /** The keepalive was successfully stopped. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onStopped() {} + /** An error occurred. */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void onError(int error) {} + } + + /** + * Allows applications to request that the system periodically send specific packets on their + * behalf, using hardware offload to save battery power. + * + * To request that the system send keepalives, call one of the methods that return a + * {@link ConnectivityManager.PacketKeepalive} object, such as {@link #startNattKeepalive}, + * passing in a non-null callback. If the callback is successfully started, the callback's + * {@code onStarted} method will be called. If an error occurs, {@code onError} will be called, + * specifying one of the {@code ERROR_*} constants in this class. + * + * To stop an existing keepalive, call {@link PacketKeepalive#stop}. The system will call + * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or + * {@link PacketKeepaliveCallback#onError} if an error occurred. + * + * @deprecated Use {@link SocketKeepalive} instead. + * + * @hide + */ + public class PacketKeepalive { + + private static final String TAG = "PacketKeepalive"; + + /** @hide */ + public static final int SUCCESS = 0; + + /** @hide */ + public static final int NO_KEEPALIVE = -1; + + /** @hide */ + public static final int BINDER_DIED = -10; + + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = -20; + /** The specified IP addresses are invalid. For example, the specified source IP address is + * not configured on the specified {@code Network}. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + /** The requested port is invalid. */ + public static final int ERROR_INVALID_PORT = -22; + /** The packet length is invalid (e.g., too long). */ + public static final int ERROR_INVALID_LENGTH = -23; + /** The packet transmission interval is invalid (e.g., too short). */ + public static final int ERROR_INVALID_INTERVAL = -24; + + /** The hardware does not support this request. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = -30; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; + + /** The NAT-T destination port for IPsec */ + public static final int NATT_PORT = 4500; + + /** The minimum interval in seconds between keepalive packet transmissions */ + public static final int MIN_INTERVAL = 10; + + private final Network mNetwork; + private final ISocketKeepaliveCallback mCallback; + private final ExecutorService mExecutor; + + private volatile Integer mSlot; + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void stop() { + try { + mExecutor.execute(() -> { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } catch (RejectedExecutionException e) { + // The internal executor has already stopped due to previous event. + } + } + + private PacketKeepalive(Network network, PacketKeepaliveCallback callback) { + Preconditions.checkNotNull(network, "network cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + mNetwork = network; + mExecutor = Executors.newSingleThreadExecutor(); + mCallback = new ISocketKeepaliveCallback.Stub() { + @Override + public void onStarted(int slot) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = slot; + callback.onStarted(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onStopped() { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = null; + callback.onStopped(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + mExecutor.shutdown(); + } + + @Override + public void onError(int error) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = null; + callback.onError(error); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + mExecutor.shutdown(); + } + + @Override + public void onDataReceived() { + // PacketKeepalive is only used for Nat-T keepalive and as such does not invoke + // this callback when data is received. + } + }; + } + } + + /** + * Starts an IPsec NAT-T keepalive packet with the specified parameters. + * + * @deprecated Use {@link #createSocketKeepalive} instead. + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public PacketKeepalive startNattKeepalive( + Network network, int intervalSeconds, PacketKeepaliveCallback callback, + InetAddress srcAddr, int srcPort, InetAddress dstAddr) { + final PacketKeepalive k = new PacketKeepalive(network, callback); + try { + mService.startNattKeepalive(network, intervalSeconds, k.mCallback, + srcAddr.getHostAddress(), srcPort, dstAddr.getHostAddress()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + return k; + } + + // Construct an invalid fd. + private ParcelFileDescriptor createInvalidFd() { + final int invalidFd = -1; + return ParcelFileDescriptor.adoptFd(invalidFd); + } + + /** + * Request that keepalives be started on a IPsec NAT-T socket. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + **/ + public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull UdpEncapsulationSocket socket, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + // Dup is needed here as the pfd inside the socket is owned by the IpSecService, + // which cannot be obtained by the app process. + dup = ParcelFileDescriptor.dup(socket.getFileDescriptor()); + } catch (IOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new NattSocketKeepalive(mService, network, dup, socket.getResourceId(), source, + destination, executor, callback); + } + + /** + * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called + * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}. + * + * @param network The {@link Network} the socket is on. + * @param pfd The {@link ParcelFileDescriptor} that needs to be kept alive. The provided + * {@link ParcelFileDescriptor} must be bound to a port and the keepalives will be sent + * from that port. + * @param source The source address of the {@link UdpEncapsulationSocket}. + * @param destination The destination address of the {@link UdpEncapsulationSocket}. The + * keepalive packets will always be sent to port 4500 of the given {@code destination}. + * @param executor The executor on which callback will be invoked. The provided {@link Executor} + * must run callback sequentially, otherwise the order of callbacks cannot be + * guaranteed. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public @NonNull SocketKeepalive createNattKeepalive(@NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + // TODO: Consider remove unnecessary dup. + dup = pfd.dup(); + } catch (IOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new NattSocketKeepalive(mService, network, dup, + INVALID_RESOURCE_ID /* Unused */, source, destination, executor, callback); + } + + /** + * Request that keepalives be started on a TCP socket. + * The socket must be established. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param executor The executor on which callback will be invoked. This implementation assumes + * the provided {@link Executor} runs the callbacks in sequence with no + * concurrency. Failing this, no guarantee of correctness can be made. It is + * the responsibility of the caller to ensure the executor provides this + * guarantee. A simple way of creating such an executor is with the standard + * tool {@code Executors.newSingleThreadExecutor}. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull Socket socket, + @NonNull Executor executor, + @NonNull Callback callback) { + ParcelFileDescriptor dup; + try { + dup = ParcelFileDescriptor.fromSocket(socket); + } catch (UncheckedIOException ignored) { + // Construct an invalid fd, so that if the user later calls start(), it will fail with + // ERROR_INVALID_SOCKET. + dup = createInvalidFd(); + } + return new TcpSocketKeepalive(mService, network, dup, executor, callback); + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + * + * @deprecated Deprecated in favor of the + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API. + * In {@link VERSION_CODES#M}, and above, this method is unsupported and will + * throw {@code UnsupportedOperationException} if called. + * @removed + */ + @Deprecated + public boolean requestRouteToHost(int networkType, int hostAddress) { + return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress)); + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the specified network interface. An attempt to add a route that + * already exists is ignored, but treated as successful. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param networkType the type of the network over which traffic to the specified + * host is to be routed + * @param hostAddress the IP address of the host to which the route is desired + * @return {@code true} on success, {@code false} on failure + * @hide + * @deprecated Deprecated in favor of the {@link #requestNetwork} and + * {@link #bindProcessToNetwork} API. + */ + @Deprecated + @UnsupportedAppUsage + public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { + checkLegacyRoutingApiAccess(); + try { + return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress(), + mContext.getOpPackageName(), getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the context's attribution tag + */ + // TODO: Remove method and replace with direct call once R code is pushed to AOSP + private @Nullable String getAttributionTag() { + return null; + } + + /** + * Returns the value of the setting for background data usage. If false, + * applications should not use the network if the application is not in the + * foreground. Developers should respect this setting, and check the value + * of this before performing any background data operations. + *

+ * All applications that have background services that use the network + * should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}. + *

+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of + * background data depends on several combined factors, and this method will + * always return {@code true}. Instead, when background data is unavailable, + * {@link #getActiveNetworkInfo()} will now appear disconnected. + * + * @return Whether background data usage is allowed. + */ + @Deprecated + public boolean getBackgroundDataSetting() { + // assume that background data is allowed; final authority is + // NetworkInfo which may be blocked. + return true; + } + + /** + * Sets the value of the setting for background data usage. + * + * @param allowBackgroundData Whether an application should use data while + * it is in the background. + * + * @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING + * @see #getBackgroundDataSetting() + * @hide + */ + @Deprecated + @UnsupportedAppUsage + public void setBackgroundDataSetting(boolean allowBackgroundData) { + // ignored + } + + /** + * @hide + * @deprecated Talk to TelephonyManager directly + */ + @Deprecated + @UnsupportedAppUsage + public boolean getMobileDataEnabled() { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + if (tm != null) { + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId); + boolean retVal = tm.createForSubscriptionId(subId).isDataEnabled(); + Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId + + " retVal=" + retVal); + return retVal; + } + Log.d("ConnectivityManager", "getMobileDataEnabled()- remote exception retVal=false"); + return false; + } + + /** + * Callback for use with {@link ConnectivityManager#addDefaultNetworkActiveListener} + * to find out when the system default network has gone in to a high power state. + */ + public interface OnNetworkActiveListener { + /** + * Called on the main thread of the process to report that the current data network + * has become active, and it is now a good time to perform any pending network + * operations. Note that this listener only tells you when the network becomes + * active; if at any other time you want to know whether it is active (and thus okay + * to initiate network traffic), you can retrieve its instantaneous state with + * {@link ConnectivityManager#isDefaultNetworkActive}. + */ + void onNetworkActive(); + } + + private INetworkManagementService getNetworkManagementService() { + synchronized (this) { + if (mNMService != null) { + return mNMService; + } + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + mNMService = INetworkManagementService.Stub.asInterface(b); + return mNMService; + } + } + + private final ArrayMap + mNetworkActivityListeners = new ArrayMap<>(); + + /** + * Start listening to reports when the system's default data network is active, meaning it is + * a good time to perform network traffic. Use {@link #isDefaultNetworkActive()} + * to determine the current state of the system's default network after registering the + * listener. + *

+ * If the process default network has been set with + * {@link ConnectivityManager#bindProcessToNetwork} this function will not + * reflect the process's default, but the system default. + * + * @param l The listener to be told when the network is active. + */ + public void addDefaultNetworkActiveListener(final OnNetworkActiveListener l) { + INetworkActivityListener rl = new INetworkActivityListener.Stub() { + @Override + public void onNetworkActive() throws RemoteException { + l.onNetworkActive(); + } + }; + + try { + getNetworkManagementService().registerNetworkActivityListener(rl); + mNetworkActivityListeners.put(l, rl); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove network active listener previously registered with + * {@link #addDefaultNetworkActiveListener}. + * + * @param l Previously registered listener. + */ + public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) { + INetworkActivityListener rl = mNetworkActivityListeners.get(l); + Preconditions.checkArgument(rl != null, "Listener was not registered."); + try { + getNetworkManagementService().unregisterNetworkActivityListener(rl); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return whether the data network is currently active. An active network means that + * it is currently in a high power state for performing data transmission. On some + * types of networks, it may be expensive to move and stay in such a state, so it is + * more power efficient to batch network traffic together when the radio is already in + * this state. This method tells you whether right now is currently a good time to + * initiate network traffic, as the network is already active. + */ + public boolean isDefaultNetworkActive() { + try { + return getNetworkManagementService().isNetworkActive(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * {@hide} + */ + public ConnectivityManager(Context context, IConnectivityManager service) { + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE); + sInstance = this; + } + + /** {@hide} */ + @UnsupportedAppUsage + public static ConnectivityManager from(Context context) { + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + /** @hide */ + public NetworkRequest getDefaultRequest() { + try { + // This is not racy as the default request is final in ConnectivityService. + return mService.getDefaultRequest(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /* TODO: These permissions checks don't belong in client-side code. Move them to + * services.jar, possibly in com.android.server.net. */ + + /** {@hide} */ + public static final void enforceChangePermission(Context context, + String callingPkg, String callingAttributionTag) { + int uid = Binder.getCallingUid(); + checkAndNoteChangeNetworkStateOperation(context, uid, callingPkg, + callingAttributionTag, true /* throwException */); + } + + /** + * Check if the package is a allowed to change the network state. This also accounts that such + * an access happened. + * + * @return {@code true} iff the package is allowed to change the network state. + */ + // TODO: Remove method and replace with direct call once R code is pushed to AOSP + private static boolean checkAndNoteChangeNetworkStateOperation(@NonNull Context context, + int uid, @NonNull String callingPackage, @Nullable String callingAttributionTag, + boolean throwException) { + return Settings.checkAndNoteChangeNetworkStateOperation(context, uid, callingPackage, + throwException); + } + + /** + * Check if the package is a allowed to write settings. This also accounts that such an access + * happened. + * + * @return {@code true} iff the package is allowed to write settings. + */ + // TODO: Remove method and replace with direct call once R code is pushed to AOSP + private static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, + @NonNull String callingPackage, @Nullable String callingAttributionTag, + boolean throwException) { + return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage, + throwException); + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + @Deprecated + static ConnectivityManager getInstanceOrNull() { + return sInstance; + } + + /** + * @deprecated - use getSystemService. This is a kludge to support static access in certain + * situations where a Context pointer is unavailable. + * @hide + */ + @Deprecated + @UnsupportedAppUsage + private static ConnectivityManager getInstance() { + if (getInstanceOrNull() == null) { + throw new IllegalStateException("No ConnectivityManager yet constructed"); + } + return getInstanceOrNull(); + } + + /** + * Get the set of tetherable, available interfaces. This list is limited by + * device configuration and current interface existence. + * + * @return an array of 0 or more Strings of tetherable interface names. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableIfaces() { + return mTetheringManager.getTetherableIfaces(); + } + + /** + * Get the set of tethered interfaces. + * + * @return an array of 0 or more String of currently tethered interface names. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfacesChanged(List)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetheredIfaces() { + return mTetheringManager.getTetheredIfaces(); + } + + /** + * Get the set of interface names which attempted to tether but + * failed. Re-attempting to tether may cause them to reset to the Tethered + * state. Alternatively, causing the interface to be destroyed and recreated + * may cause them to reset to the available state. + * {@link ConnectivityManager#getLastTetherError} can be used to get more + * information on the cause of the errors. + * + * @return an array of 0 or more String indicating the interface names + * which failed to tether. + * + * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetheringErroredIfaces() { + return mTetheringManager.getTetheringErroredIfaces(); + } + + /** + * Get the set of tethered dhcp ranges. + * + * @deprecated This method is not supported. + * TODO: remove this function when all of clients are removed. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + @Deprecated + public String[] getTetheredDhcpRanges() { + throw new UnsupportedOperationException("getTetheredDhcpRanges is not supported"); + } + + /** + * Attempt to tether the named interface. This will setup a dhcp server + * on the interface, forward and NAT IP packets and forward DNS requests + * to the best active upstream network interface. Note that if no upstream + * IP network interface is available, dhcp will still run and traffic will be + * allowed between the tethered devices and this device, though upstream net + * access will of course fail until an upstream network interface becomes + * active. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

WARNING: New clients should not use this function. The only usages should be in PanService + * and WifiStateMachine which need direct access. All other clients should use + * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning + * logic.

+ * + * @param iface the interface name to tether. + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * @deprecated Use {@link TetheringManager#startTethering} instead + * + * {@hide} + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated + public int tether(String iface) { + return mTetheringManager.tether(iface); + } + + /** + * Stop tethering the named interface. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

WARNING: New clients should not use this function. The only usages should be in PanService + * and WifiStateMachine which need direct access. All other clients should use + * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning + * logic.

+ * + * @param iface the interface name to untether. + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * + * {@hide} + */ + @UnsupportedAppUsage + @Deprecated + public int untether(String iface) { + return mTetheringManager.untether(iface); + } + + /** + * Check if the device allows for tethering. It may be disabled via + * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or + * due to device configuration. + * + *

If this app does not have permission to use this API, it will always + * return false rather than throw an exception.

+ * + *

If the device has a hotspot provisioning app, the caller is required to hold the + * {@link android.Manifest.permission.TETHER_PRIVILEGED} permission.

+ * + *

Otherwise, this method requires the caller to hold the ability to modify system + * settings as determined by {@link android.provider.Settings.System#canWrite}.

+ * + * @return a boolean - {@code true} indicating Tethering is supported. + * + * @deprecated Use {@link TetheringEventCallback#onTetheringSupported(boolean)} instead. + * {@hide} + */ + @SystemApi + @RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED, + android.Manifest.permission.WRITE_SETTINGS}) + public boolean isTetheringSupported() { + return mTetheringManager.isTetheringSupported(); + } + + /** + * Callback for use with {@link #startTethering} to find out whether tethering succeeded. + * + * @deprecated Use {@link TetheringManager.StartTetheringCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + public static abstract class OnStartTetheringCallback { + /** + * Called when tethering has been successfully started. + */ + public void onTetheringStarted() {} + + /** + * Called when starting tethering failed. + */ + public void onTetheringFailed() {} + } + + /** + * Convenient overload for + * {@link #startTethering(int, boolean, OnStartTetheringCallback, Handler)} which passes a null + * handler to run on the current thread's {@link Looper}. + * + * @deprecated Use {@link TetheringManager#startTethering} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void startTethering(int type, boolean showProvisioningUi, + final OnStartTetheringCallback callback) { + startTethering(type, showProvisioningUi, callback, null); + } + + /** + * Runs tether provisioning for the given type if needed and then starts tethering if + * the check succeeds. If no carrier provisioning is required for tethering, tethering is + * enabled immediately. If provisioning fails, tethering will not be enabled. It also + * schedules tether provisioning re-checks if appropriate. + * + * @param type The type of tethering to start. Must be one of + * {@link ConnectivityManager.TETHERING_WIFI}, + * {@link ConnectivityManager.TETHERING_USB}, or + * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * @param showProvisioningUi a boolean indicating to show the provisioning app UI if there + * is one. This should be true the first time this function is called and also any time + * the user can see this UI. It gives users information from their carrier about the + * check failing and how they can sign up for tethering if possible. + * @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller + * of the result of trying to tether. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * + * @deprecated Use {@link TetheringManager#startTethering} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void startTethering(int type, boolean showProvisioningUi, + final OnStartTetheringCallback callback, Handler handler) { + Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null."); + + final Executor executor = new Executor() { + @Override + public void execute(Runnable command) { + if (handler == null) { + command.run(); + } else { + handler.post(command); + } + } + }; + + final StartTetheringCallback tetheringCallback = new StartTetheringCallback() { + @Override + public void onTetheringStarted() { + callback.onTetheringStarted(); + } + + @Override + public void onTetheringFailed(final int error) { + callback.onTetheringFailed(); + } + }; + + final TetheringRequest request = new TetheringRequest.Builder(type) + .setShouldShowEntitlementUi(showProvisioningUi).build(); + + mTetheringManager.startTethering(request, executor, tetheringCallback); + } + + /** + * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if + * applicable. + * + * @param type The type of tethering to stop. Must be one of + * {@link ConnectivityManager.TETHERING_WIFI}, + * {@link ConnectivityManager.TETHERING_USB}, or + * {@link ConnectivityManager.TETHERING_BLUETOOTH}. + * + * @deprecated Use {@link TetheringManager#stopTethering} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void stopTethering(int type) { + mTetheringManager.stopTethering(type); + } + + /** + * Callback for use with {@link registerTetheringEventCallback} to find out tethering + * upstream status. + * + * @deprecated Use {@link TetheringManager#OnTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + public abstract static class OnTetheringEventCallback { + + /** + * Called when tethering upstream changed. This can be called multiple times and can be + * called any time. + * + * @param network the {@link Network} of tethering upstream. Null means tethering doesn't + * have any upstream. + */ + public void onUpstreamChanged(@Nullable Network network) {} + } + + @GuardedBy("mTetheringEventCallbacks") + private final ArrayMap + mTetheringEventCallbacks = new ArrayMap<>(); + + /** + * Start listening to tethering change events. Any new added callback will receive the last + * tethering status right away. If callback is registered when tethering has no upstream or + * disabled, {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called + * with a null argument. The same callback object cannot be registered twice. + * + * @param executor the executor on which callback will be invoked. + * @param callback the callback to be called when tethering has change events. + * + * @deprecated Use {@link TetheringManager#registerTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void registerTetheringEventCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull final OnTetheringEventCallback callback) { + Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null."); + + final TetheringEventCallback tetherCallback = + new TetheringEventCallback() { + @Override + public void onUpstreamChanged(@Nullable Network network) { + callback.onUpstreamChanged(network); + } + }; + + synchronized (mTetheringEventCallbacks) { + mTetheringEventCallbacks.put(callback, tetherCallback); + mTetheringManager.registerTetheringEventCallback(executor, tetherCallback); + } + } + + /** + * Remove tethering event callback previously registered with + * {@link #registerTetheringEventCallback}. + * + * @param callback previously registered callback. + * + * @deprecated Use {@link TetheringManager#unregisterTetheringEventCallback} instead. + * @hide + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void unregisterTetheringEventCallback( + @NonNull final OnTetheringEventCallback callback) { + Objects.requireNonNull(callback, "The callback must be non-null"); + synchronized (mTetheringEventCallbacks) { + final TetheringEventCallback tetherCallback = + mTetheringEventCallbacks.remove(callback); + mTetheringManager.unregisterTetheringEventCallback(tetherCallback); + } + } + + + /** + * Get the list of regular expressions that define any tetherable + * USB network interfaces. If USB tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable usb interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableUsbRegexs() { + return mTetheringManager.getTetherableUsbRegexs(); + } + + /** + * Get the list of regular expressions that define any tetherable + * Wifi network interfaces. If Wifi tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable wifi interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableWifiRegexs() { + return mTetheringManager.getTetherableWifiRegexs(); + } + + /** + * Get the list of regular expressions that define any tetherable + * Bluetooth network interfaces. If Bluetooth tethering is not supported by the + * device, this list should be empty. + * + * @return an array of 0 or more regular expression Strings defining + * what interfaces are considered tetherable bluetooth interfaces. + * + * @deprecated Use {@link TetheringEventCallback#onTetherableInterfaceRegexpsChanged( + *TetheringManager.TetheringInterfaceRegexps)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage + @Deprecated + public String[] getTetherableBluetoothRegexs() { + return mTetheringManager.getTetherableBluetoothRegexs(); + } + + /** + * Attempt to both alter the mode of USB and Tethering of USB. A + * utility method to deal with some of the complexity of USB - will + * attempt to switch to Rndis and subsequently tether the resulting + * interface on {@code true} or turn off tethering and switch off + * Rndis on {@code false}. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param enable a boolean - {@code true} to enable tethering + * @return error a {@code TETHER_ERROR} value indicating success or failure type + * @deprecated Use {@link TetheringManager#startTethering} instead + * + * {@hide} + */ + @UnsupportedAppUsage + @Deprecated + public int setUsbTethering(boolean enable) { + return mTetheringManager.setUsbTethering(enable); + } + + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_NO_ERROR}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNKNOWN_IFACE = + TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_SERVICE_UNAVAIL}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_SERVICE_UNAVAIL = + TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNSUPPORTED}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNSUPPORTED = TetheringManager.TETHER_ERROR_UNSUPPORTED; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNAVAIL_IFACE}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNAVAIL_IFACE = + TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_INTERNAL_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_MASTER_ERROR = + TetheringManager.TETHER_ERROR_INTERNAL_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_TETHER_IFACE_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_TETHER_IFACE_ERROR = + TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNTETHER_IFACE_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = + TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENABLE_FORWARDING_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_ENABLE_NAT_ERROR = + TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_DISABLE_FORWARDING_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_DISABLE_NAT_ERROR = + TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_IFACE_CFG_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_IFACE_CFG_ERROR = + TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_PROVISIONING_FAILED}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_PROVISION_FAILED = + TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}. + * {@hide} + */ + @Deprecated + public static final int TETHER_ERROR_DHCPSERVER_ERROR = + TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; + /** + * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENTITLEMENT_UNKNOWN}. + * {@hide} + */ + @SystemApi + @Deprecated + public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = + TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; + + /** + * Get a more detailed error code after a Tethering or Untethering + * request asynchronously failed. + * + * @param iface The name of the interface of interest + * @return error The error code of the last error tethering or untethering the named + * interface + * + * @deprecated Use {@link TetheringEventCallback#onError(String, int)} instead. + * {@hide} + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated + public int getLastTetherError(String iface) { + int error = mTetheringManager.getLastTetherError(iface); + if (error == TetheringManager.TETHER_ERROR_UNKNOWN_TYPE) { + // TETHER_ERROR_UNKNOWN_TYPE was introduced with TetheringManager and has never been + // returned by ConnectivityManager. Convert it to the legacy TETHER_ERROR_UNKNOWN_IFACE + // instead. + error = TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; + } + return error; + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + TETHER_ERROR_NO_ERROR, + TETHER_ERROR_PROVISION_FAILED, + TETHER_ERROR_ENTITLEMENT_UNKONWN, + }) + public @interface EntitlementResultCode { + } + + /** + * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether + * entitlement succeeded. + * + * @deprecated Use {@link TetheringManager#OnTetheringEntitlementResultListener} instead. + * @hide + */ + @SystemApi + @Deprecated + public interface OnTetheringEntitlementResultListener { + /** + * Called to notify entitlement result. + * + * @param resultCode an int value of entitlement result. It may be one of + * {@link #TETHER_ERROR_NO_ERROR}, + * {@link #TETHER_ERROR_PROVISION_FAILED}, or + * {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}. + */ + void onTetheringEntitlementResult(@EntitlementResultCode int resultCode); + } + + /** + * Get the last value of the entitlement check on this downstream. If the cached value is + * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, it just return the + * cached value. Otherwise, a UI-based entitlement check would be performed. It is not + * guaranteed that the UI-based entitlement check will complete in any specific time period + * and may in fact never complete. Any successful entitlement check the platform performs for + * any reason will update the cached value. + * + * @param type the downstream type of tethering. Must be one of + * {@link #TETHERING_WIFI}, + * {@link #TETHERING_USB}, or + * {@link #TETHERING_BLUETOOTH}. + * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check. + * @param executor the executor on which callback will be invoked. + * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to + * notify the caller of the result of entitlement check. The listener may be called zero + * or one time. + * @deprecated Use {@link TetheringManager#requestLatestTetheringEntitlementResult} instead. + * {@hide} + */ + @SystemApi + @Deprecated + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi, + @NonNull @CallbackExecutor Executor executor, + @NonNull final OnTetheringEntitlementResultListener listener) { + Preconditions.checkNotNull(listener, "TetheringEntitlementResultListener cannot be null."); + ResultReceiver wrappedListener = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + Binder.withCleanCallingIdentity(() -> + executor.execute(() -> { + listener.onTetheringEntitlementResult(resultCode); + })); + } + }; + + mTetheringManager.requestLatestTetheringEntitlementResult(type, wrappedListener, + showEntitlementUi); + } + + /** + * Report network connectivity status. This is currently used only + * to alter status bar UI. + *

This method requires the caller to hold the permission + * {@link android.Manifest.permission#STATUS_BAR}. + * + * @param networkType The type of network you want to report on + * @param percentage The quality of the connection 0 is bad, 100 is good + * @deprecated Types are deprecated. Use {@link #reportNetworkConnectivity} instead. + * {@hide} + */ + public void reportInetCondition(int networkType, int percentage) { + printStackTrace(); + try { + mService.reportInetCondition(networkType, percentage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Report a problem network to the framework. This provides a hint to the system + * that there might be connectivity problems on this network and may cause + * the framework to re-evaluate network connectivity and/or switch to another + * network. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both + * working and non-working connectivity. + */ + @Deprecated + public void reportBadNetwork(@Nullable Network network) { + printStackTrace(); + try { + // One of these will be ignored because it matches system's current state. + // The other will trigger the necessary reevaluation. + mService.reportNetworkConnectivity(network, true); + mService.reportNetworkConnectivity(network, false); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Report to the framework whether a network has working connectivity. + * This provides a hint to the system that a particular network is providing + * working connectivity or not. In response the framework may re-evaluate + * the network's connectivity and might take further action thereafter. + * + * @param network The {@link Network} the application was attempting to use + * or {@code null} to indicate the current default network. + * @param hasConnectivity {@code true} if the application was able to successfully access the + * Internet using {@code network} or {@code false} if not. + */ + public void reportNetworkConnectivity(@Nullable Network network, boolean hasConnectivity) { + printStackTrace(); + try { + mService.reportNetworkConnectivity(network, hasConnectivity); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set a network-independent global http proxy. This is not normally what you want + * for typical HTTP proxies - they are general network dependent. However if you're + * doing something unusual like general internal filtering this may be useful. On + * a private network where the proxy is not accessible, you may break HTTP using this. + * + * @param p A {@link ProxyInfo} object defining the new global + * HTTP proxy. A {@code null} value will clear the global HTTP proxy. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setGlobalProxy(ProxyInfo p) { + try { + mService.setGlobalProxy(p); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve any network-independent global HTTP proxy. + * + * @return {@link ProxyInfo} for the current global HTTP proxy or {@code null} + * if no global HTTP proxy is set. + * @hide + */ + public ProxyInfo getGlobalProxy() { + try { + return mService.getGlobalProxy(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieve the global HTTP proxy, or if no global HTTP proxy is set, a + * network-specific HTTP proxy. If {@code network} is null, the + * network-specific proxy returned is the proxy of the default active + * network. + * + * @return {@link ProxyInfo} for the current global HTTP proxy, or if no + * global HTTP proxy is set, {@code ProxyInfo} for {@code network}, + * or when {@code network} is {@code null}, + * the {@code ProxyInfo} for the default active network. Returns + * {@code null} when no proxy applies or the caller doesn't have + * permission to use {@code network}. + * @hide + */ + public ProxyInfo getProxyForNetwork(Network network) { + try { + return mService.getProxyForNetwork(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the current default HTTP proxy settings. If a global proxy is set it will be returned, + * otherwise if this process is bound to a {@link Network} using + * {@link #bindProcessToNetwork} then that {@code Network}'s proxy is returned, otherwise + * the default network's proxy is returned. + * + * @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no + * HTTP proxy is active. + */ + @Nullable + public ProxyInfo getDefaultProxy() { + return getProxyForNetwork(getBoundNetworkForProcess()); + } + + /** + * Returns true if the hardware supports the given network type + * else it returns false. This doesn't indicate we have coverage + * or are authorized onto a network, just whether or not the + * hardware supports it. For example a GSM phone without a SIM + * should still return {@code true} for mobile data, but a wifi only + * tablet would return {@code false}. + * + * @param networkType The network type we'd like to check + * @return {@code true} if supported, else {@code false} + * @deprecated Types are deprecated. Use {@link NetworkCapabilities} instead. + * @hide + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) + public boolean isNetworkSupported(int networkType) { + try { + return mService.isNetworkSupported(networkType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns if the currently active data network is metered. A network is + * classified as metered when the user is sensitive to heavy data usage on + * that connection due to monetary costs, data limitations or + * battery/performance issues. You should check this before doing large + * data transfers, and warn the user or delay the operation until another + * network is available. + * + * @return {@code true} if large transfers should be avoided, otherwise + * {@code false}. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public boolean isActiveNetworkMetered() { + try { + return mService.isActiveNetworkMetered(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * If the LockdownVpn mechanism is enabled, updates the vpn + * with a reload of its profile. + * + * @return a boolean with {@code} indicating success + * + *

This method can only be called by the system UID + * {@hide} + */ + public boolean updateLockdownVpn() { + try { + return mService.updateLockdownVpn(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set sign in error notification to visible or invisible + * + * @hide + * @deprecated Doesn't properly deal with multiple connected networks of the same type. + */ + @Deprecated + public void setProvisioningNotificationVisible(boolean visible, int networkType, + String action) { + try { + mService.setProvisioningNotificationVisible(visible, networkType, action); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set the value for enabling/disabling airplane mode + * + * @param enable whether to enable airplane mode or not + * + * @hide + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_AIRPLANE_MODE, + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK}) + @SystemApi + public void setAirplaneMode(boolean enable) { + try { + mService.setAirplaneMode(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} - returns the factory serial number */ + @UnsupportedAppUsage + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public int registerNetworkFactory(Messenger messenger, String name) { + try { + return mService.registerNetworkFactory(messenger, name); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void unregisterNetworkFactory(Messenger messenger) { + try { + mService.unregisterNetworkFactory(messenger); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers the specified {@link NetworkProvider}. + * Each listener must only be registered once. The listener can be unregistered with + * {@link #unregisterNetworkProvider}. + * + * @param provider the provider to register + * @return the ID of the provider. This ID must be used by the provider when registering + * {@link android.net.NetworkAgent}s. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public int registerNetworkProvider(@NonNull NetworkProvider provider) { + if (provider.getProviderId() != NetworkProvider.ID_NONE) { + throw new IllegalStateException("NetworkProviders can only be registered once"); + } + + try { + int providerId = mService.registerNetworkProvider(provider.getMessenger(), + provider.getName()); + provider.setProviderId(providerId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return provider.getProviderId(); + } + + /** + * Unregisters the specified NetworkProvider. + * + * @param provider the provider to unregister + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void unregisterNetworkProvider(@NonNull NetworkProvider provider) { + try { + mService.unregisterNetworkProvider(provider.getMessenger()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + provider.setProviderId(NetworkProvider.ID_NONE); + } + + + /** @hide exposed via the NetworkProvider class. */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { + try { + mService.declareNetworkRequestUnfulfillable(request); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + // TODO : remove this method. It is a stopgap measure to help sheperding a number + // of dependent changes that would conflict throughout the automerger graph. Having this + // temporarily helps with the process of going through with all these dependent changes across + // the entire tree. + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return Network corresponding to NetworkAgent. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, + NetworkCapabilities nc, int score, NetworkAgentConfig config) { + return registerNetworkAgent(na, ni, lp, nc, score, config, NetworkProvider.ID_NONE); + } + + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return Network corresponding to NetworkAgent. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, + NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) { + try { + return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Base class for {@code NetworkRequest} callbacks. Used for notifications about network + * changes. Should be extended by applications wanting notifications. + * + * A {@code NetworkCallback} is registered by calling + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)}, + * or {@link #registerDefaultNetworkCallback(NetworkCallback)}. A {@code NetworkCallback} is + * unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}. + * A {@code NetworkCallback} should be registered at most once at any time. + * A {@code NetworkCallback} that has been unregistered can be registered again. + */ + public static class NetworkCallback { + /** + * Called when the framework connects to a new network to evaluate whether it satisfies this + * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable} + * callback. There is no guarantee that this new network will satisfy any requests, or that + * the network will stay connected for longer than the time necessary to evaluate it. + *

+ * Most applications should not act on this callback, and should instead use + * {@link #onAvailable}. This callback is intended for use by applications that can assist + * the framework in properly evaluating the network — for example, an application that + * can automatically log in to a captive portal without user intervention. + * + * @param network The {@link Network} of the network that is being evaluated. + * + * @hide + */ + public void onPreCheck(@NonNull Network network) {} + + /** + * Called when the framework connects and has declared a new network ready for use. + * This callback may be called more than once if the {@link Network} that is + * satisfying the request changes. + * + * @param network The {@link Network} of the satisfying network. + * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network. + * @param linkProperties The {@link LinkProperties} of the satisfying network. + * @param blocked Whether access to the {@link Network} is blocked due to system policy. + * @hide + */ + public void onAvailable(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, boolean blocked) { + // Internally only this method is called when a new network is available, and + // it calls the callback in the same way and order that older versions used + // to call so as not to change the behavior. + onAvailable(network); + if (!networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)) { + onNetworkSuspended(network); + } + onCapabilitiesChanged(network, networkCapabilities); + onLinkPropertiesChanged(network, linkProperties); + onBlockedStatusChanged(network, blocked); + } + + /** + * Called when the framework connects and has declared a new network ready for use. + * + *

For callbacks registered with {@link #registerNetworkCallback}, multiple networks may + * be available at the same time, and onAvailable will be called for each of these as they + * appear. + * + *

For callbacks registered with {@link #requestNetwork} and + * {@link #registerDefaultNetworkCallback}, this means the network passed as an argument + * is the new best network for this request and is now tracked by this callback ; this + * callback will no longer receive method calls about other networks that may have been + * passed to this method previously. The previously-best network may have disconnected, or + * it may still be around and the newly-best network may simply be better. + * + *

Starting with {@link android.os.Build.VERSION_CODES#O}, this will always immediately + * be followed by a call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} + * then by a call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call + * to {@link #onBlockedStatusChanged(Network, boolean)}. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions (there is no guarantee the objects + * returned by these methods will be current). Instead, wait for a call to + * {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link #onLinkPropertiesChanged(Network, LinkProperties)} whose arguments are guaranteed + * to be well-ordered with respect to other callbacks. + * + * @param network The {@link Network} of the satisfying network. + */ + public void onAvailable(@NonNull Network network) {} + + /** + * Called when the network is about to be lost, typically because there are no outstanding + * requests left for it. This may be paired with a {@link NetworkCallback#onAvailable} call + * with the new replacement network for graceful handover. This method is not guaranteed + * to be called before {@link NetworkCallback#onLost} is called, for example in case a + * network is suddenly disconnected. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions ; calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} that is about to be lost. + * @param maxMsToLive The time in milliseconds the system intends to keep the network + * connected for graceful handover; note that the network may still + * suffer a hard loss at any time. + */ + public void onLosing(@NonNull Network network, int maxMsToLive) {} + + /** + * Called when a network disconnects or otherwise no longer satisfies this request or + * callback. + * + *

If the callback was registered with requestNetwork() or + * registerDefaultNetworkCallback(), it will only be invoked against the last network + * returned by onAvailable() when that network is lost and no other network satisfies + * the criteria of the request. + * + *

If the callback was registered with registerNetworkCallback() it will be called for + * each network which no longer satisfies the criteria of the callback. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions ; calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} lost. + */ + public void onLost(@NonNull Network network) {} + + /** + * Called if no network is found within the timeout time specified in + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the + * requested network request cannot be fulfilled (whether or not a timeout was + * specified). When this callback is invoked the associated + * {@link NetworkRequest} will have already been removed and released, as if + * {@link #unregisterNetworkCallback(NetworkCallback)} had been called. + */ + public void onUnavailable() {} + + /** + * Called when the network corresponding to this request changes capabilities but still + * satisfies the requested criteria. + * + *

Starting with {@link android.os.Build.VERSION_CODES#O} this method is guaranteed + * to be called immediately after {@link #onAvailable}. + * + *

Do NOT call {@link #getLinkProperties(Network)} or other synchronous + * ConnectivityManager methods in this callback as this is prone to race conditions : + * calling these methods while in a callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose capabilities have changed. + * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this + * network. + */ + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities) {} + + /** + * Called when the network corresponding to this request changes {@link LinkProperties}. + * + *

Starting with {@link android.os.Build.VERSION_CODES#O} this method is guaranteed + * to be called immediately after {@link #onAvailable}. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or other synchronous + * ConnectivityManager methods in this callback as this is prone to race conditions : + * calling these methods while in a callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose link properties have changed. + * @param linkProperties The new {@link LinkProperties} for this network. + */ + public void onLinkPropertiesChanged(@NonNull Network network, + @NonNull LinkProperties linkProperties) {} + + /** + * Called when the network the framework connected to for this request suspends data + * transmission temporarily. + * + *

This generally means that while the TCP connections are still live temporarily + * network data fails to transfer. To give a specific example, this is used on cellular + * networks to mask temporary outages when driving through a tunnel, etc. In general this + * means read operations on sockets on this network will block once the buffers are + * drained, and write operations will block once the buffers are full. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions (there is no guarantee the objects + * returned by these methods will be current). + * + * @hide + */ + public void onNetworkSuspended(@NonNull Network network) {} + + /** + * Called when the network the framework connected to for this request + * returns from a {@link NetworkInfo.State#SUSPENDED} state. This should always be + * preceded by a matching {@link NetworkCallback#onNetworkSuspended} call. + + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions : calling these methods while in a + * callback may return an outdated or even a null object. + * + * @hide + */ + public void onNetworkResumed(@NonNull Network network) {} + + /** + * Called when access to the specified network is blocked or unblocked. + * + *

Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions : calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose blocked status has changed. + * @param blocked The blocked status of this {@link Network}. + */ + public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {} + + private NetworkRequest networkRequest; + } + + /** + * Constant error codes used by ConnectivityService to communicate about failures and errors + * across a Binder boundary. + * @hide + */ + public interface Errors { + int TOO_MANY_REQUESTS = 1; + } + + /** @hide */ + public static class TooManyRequestsException extends RuntimeException {} + + private static RuntimeException convertServiceException(ServiceSpecificException e) { + switch (e.errorCode) { + case Errors.TOO_MANY_REQUESTS: + return new TooManyRequestsException(); + default: + Log.w(TAG, "Unknown service error code " + e.errorCode); + return new RuntimeException(e); + } + } + + private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER; + /** @hide */ + public static final int CALLBACK_PRECHECK = BASE + 1; + /** @hide */ + public static final int CALLBACK_AVAILABLE = BASE + 2; + /** @hide arg1 = TTL */ + public static final int CALLBACK_LOSING = BASE + 3; + /** @hide */ + public static final int CALLBACK_LOST = BASE + 4; + /** @hide */ + public static final int CALLBACK_UNAVAIL = BASE + 5; + /** @hide */ + public static final int CALLBACK_CAP_CHANGED = BASE + 6; + /** @hide */ + public static final int CALLBACK_IP_CHANGED = BASE + 7; + /** @hide obj = NetworkCapabilities, arg1 = seq number */ + private static final int EXPIRE_LEGACY_REQUEST = BASE + 8; + /** @hide */ + public static final int CALLBACK_SUSPENDED = BASE + 9; + /** @hide */ + public static final int CALLBACK_RESUMED = BASE + 10; + /** @hide */ + public static final int CALLBACK_BLK_CHANGED = BASE + 11; + + /** @hide */ + public static String getCallbackName(int whichCallback) { + switch (whichCallback) { + case CALLBACK_PRECHECK: return "CALLBACK_PRECHECK"; + case CALLBACK_AVAILABLE: return "CALLBACK_AVAILABLE"; + case CALLBACK_LOSING: return "CALLBACK_LOSING"; + case CALLBACK_LOST: return "CALLBACK_LOST"; + case CALLBACK_UNAVAIL: return "CALLBACK_UNAVAIL"; + case CALLBACK_CAP_CHANGED: return "CALLBACK_CAP_CHANGED"; + case CALLBACK_IP_CHANGED: return "CALLBACK_IP_CHANGED"; + case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST"; + case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED"; + case CALLBACK_RESUMED: return "CALLBACK_RESUMED"; + case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED"; + default: + return Integer.toString(whichCallback); + } + } + + private class CallbackHandler extends Handler { + private static final String TAG = "ConnectivityManager.CallbackHandler"; + private static final boolean DBG = false; + + CallbackHandler(Looper looper) { + super(looper); + } + + CallbackHandler(Handler handler) { + this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper()); + } + + @Override + public void handleMessage(Message message) { + if (message.what == EXPIRE_LEGACY_REQUEST) { + expireRequest((NetworkCapabilities) message.obj, message.arg1); + return; + } + + final NetworkRequest request = getObject(message, NetworkRequest.class); + final Network network = getObject(message, Network.class); + final NetworkCallback callback; + synchronized (sCallbacks) { + callback = sCallbacks.get(request); + if (callback == null) { + Log.w(TAG, + "callback not found for " + getCallbackName(message.what) + " message"); + return; + } + if (message.what == CALLBACK_UNAVAIL) { + sCallbacks.remove(request); + callback.networkRequest = ALREADY_UNREGISTERED; + } + } + if (DBG) { + Log.d(TAG, getCallbackName(message.what) + " for network " + network); + } + + switch (message.what) { + case CALLBACK_PRECHECK: { + callback.onPreCheck(network); + break; + } + case CALLBACK_AVAILABLE: { + NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); + LinkProperties lp = getObject(message, LinkProperties.class); + callback.onAvailable(network, cap, lp, message.arg1 != 0); + break; + } + case CALLBACK_LOSING: { + callback.onLosing(network, message.arg1); + break; + } + case CALLBACK_LOST: { + callback.onLost(network); + break; + } + case CALLBACK_UNAVAIL: { + callback.onUnavailable(); + break; + } + case CALLBACK_CAP_CHANGED: { + NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); + callback.onCapabilitiesChanged(network, cap); + break; + } + case CALLBACK_IP_CHANGED: { + LinkProperties lp = getObject(message, LinkProperties.class); + callback.onLinkPropertiesChanged(network, lp); + break; + } + case CALLBACK_SUSPENDED: { + callback.onNetworkSuspended(network); + break; + } + case CALLBACK_RESUMED: { + callback.onNetworkResumed(network); + break; + } + case CALLBACK_BLK_CHANGED: { + boolean blocked = message.arg1 != 0; + callback.onBlockedStatusChanged(network, blocked); + } + } + } + + private T getObject(Message msg, Class c) { + return (T) msg.getData().getParcelable(c.getSimpleName()); + } + } + + private CallbackHandler getDefaultHandler() { + synchronized (sCallbacks) { + if (sCallbackHandler == null) { + sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper()); + } + return sCallbackHandler; + } + } + + private static final HashMap sCallbacks = new HashMap<>(); + private static CallbackHandler sCallbackHandler; + + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, + int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + printStackTrace(); + checkCallbackNotNull(callback); + Preconditions.checkArgument( + reqType == TRACK_DEFAULT || need != null, "null NetworkCapabilities"); + final NetworkRequest request; + final String callingPackageName = mContext.getOpPackageName(); + try { + synchronized(sCallbacks) { + if (callback.networkRequest != null + && callback.networkRequest != ALREADY_UNREGISTERED) { + // TODO: throw exception instead and enforce 1:1 mapping of callbacks + // and requests (http://b/20701525). + Log.e(TAG, "NetworkCallback was already registered"); + } + Messenger messenger = new Messenger(handler); + Binder binder = new Binder(); + if (reqType == LISTEN) { + request = mService.listenForNetwork( + need, messenger, binder, callingPackageName); + } else { + request = mService.requestNetwork( + need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType, + callingPackageName, getAttributionTag()); + } + if (request != null) { + sCallbacks.put(request, callback); + } + callback.networkRequest = request; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + return request; + } + + /** + * Helper function to request a network with a particular legacy type. + * + * This API is only for use in internal system code that requests networks with legacy type and + * relies on CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use + * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead. + * + * @param request {@link NetworkRequest} describing this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + * @param legacyType to specify the network type(#TYPE_*). + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * + * @hide + */ + @SystemApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void requestNetwork(@NonNull NetworkRequest request, + int timeoutMs, int legacyType, @NonNull Handler handler, + @NonNull NetworkCallback networkCallback) { + if (legacyType == TYPE_NONE) { + throw new IllegalArgumentException("TYPE_NONE is meaningless legacy type"); + } + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + *

This method will attempt to find the best network that matches the passed + * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the + * criteria. The platform will evaluate which network is the best at its own discretion. + * Throughput, latency, cost per byte, policy, user preference and other considerations + * may be factored in the decision of what is considered the best network. + * + *

As long as this request is outstanding, the platform will try to maintain the best network + * matching this request, while always attempting to match the request to a better network if + * possible. If a better match is found, the platform will switch this request to the now-best + * network and inform the app of the newly best network by invoking + * {@link NetworkCallback#onAvailable(Network)} on the provided callback. Note that the platform + * will not try to maintain any other network than the best one currently matching the request: + * a network not matching any network request may be disconnected at any time. + * + *

For example, an application could use this method to obtain a connected cellular network + * even if the device currently has a data connection over Ethernet. This may cause the cellular + * radio to consume additional power. Or, an application could inform the system that it wants + * a network supporting sending MMSes and have the system let it know about the currently best + * MMS-supporting network through the provided {@link NetworkCallback}. + * + *

The status of the request can be followed by listening to the various callbacks described + * in {@link NetworkCallback}. The {@link Network} object passed to the callback methods can be + * used to direct traffic to the network (although accessing some networks may be subject to + * holding specific permissions). Callers will learn about the specific characteristics of the + * network through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)}. The methods of the + * provided {@link NetworkCallback} will only be invoked due to changes in the best network + * matching the request at any given time; therefore when a better network matching the request + * becomes available, the {@link NetworkCallback#onAvailable(Network)} method is called + * with the new network after which no further updates are given about the previously-best + * network, unless it becomes the best again at some later time. All callbacks are invoked + * in order on the same thread, which by default is a thread created by the framework running + * in the app. + * {@see #requestNetwork(NetworkRequest, NetworkCallback, Handler)} to change where the + * callbacks are invoked. + * + *

This{@link NetworkRequest} will live until released via + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits, at + * which point the system may let go of the network at any time. + * + *

A version of this method which takes a timeout is + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}, that an app can use to only + * wait for a limited amount of time for the network to become unavailable. + * + *

It is presently unsupported to request a network with mutable + * {@link NetworkCapabilities} such as + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * The callback is invoked on the default internal Handler. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback) { + requestNetwork(request, networkCallback, getDefaultHandler()); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + * This method behaves identically to {@link #requestNetwork(NetworkRequest, NetworkCallback)} + * but runs all the callbacks on the passed Handler. + * + *

This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * by a timeout. + * + * This function behaves identically to the non-timed-out version + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network + * is not found within the given time (in milliseconds) the + * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be + * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does + * not have to be released if timed-out (it is automatically released). Unregistering a + * request that timed out is not an error. + * + *

Do not use this method to poll for the existence of specific networks (e.g. with a small + * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided + * for that purpose. Calling this method will attempt to bring up the requested network. + * + *

This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, int timeoutMs) { + checkTimeout(timeoutMs); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, TYPE_NONE, + getDefaultHandler()); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * by a timeout. + * + * This method behaves identically to + * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} but runs all the callbacks + * on the passed Handler. + * + *

This method has the same permission requirements as + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, is subject to the same limitations, + * and throws the same exceptions in the same conditions. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable} is called. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) { + checkTimeout(timeoutMs); + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, TYPE_NONE, cbHandler); + } + + /** + * The lookup key for a {@link Network} object included with the intent after + * successfully finding a network for the applications request. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + *

+ * Note that if you intend to invoke {@link Network#openConnection(java.net.URL)} + * then you must get a ConnectivityManager instance before doing so. + */ + public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; + + /** + * The lookup key for a {@link NetworkRequest} object included with the intent after + * successfully finding a network for the applications request. Retrieve it with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; + + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * + * This function behaves identically to the version that takes a NetworkCallback, but instead + * of {@link NetworkCallback} a {@link PendingIntent} is used. This means + * the request may outlive the calling application and get called back when a suitable + * network is found. + *

+ * The operation is an Intent broadcast that goes to a broadcast receiver that + * you registered with {@link Context#registerReceiver} or through the + * <receiver> tag in an AndroidManifest.xml file + *

+ * The operation Intent is delivered with two extras, a {@link Network} typed + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing + * the original requests parameters. It is important to create a new, + * {@link NetworkCallback} based request before completing the processing of the + * Intent to reserve the network or it will be released shortly after the Intent + * is processed. + *

+ * If there is already a request for this Intent registered (with the equality of + * two Intents defined by {@link Intent#filterEquals}), then it will be removed and + * replaced by this one, effectively releasing the previous {@link NetworkRequest}. + *

+ * The request may be released normally by calling + * {@link #releaseNetworkRequest(android.app.PendingIntent)}. + *

It is presently unsupported to request a network with either + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with {@link #unregisterNetworkCallback(PendingIntent)} + * or {@link #releaseNetworkRequest(PendingIntent)}. + * + *

This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.

+ * + * @param request {@link NetworkRequest} describing this request. + * @param operation Action to perform when the network is available (corresponds + * to the {@link NetworkCallback#onAvailable} call. Typically + * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + public void requestNetwork(@NonNull NetworkRequest request, + @NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.pendingRequestForNetwork( + request.networkCapabilities, operation, mContext.getOpPackageName(), + getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + } + + /** + * Removes a request made via {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} + *

+ * This method has the same behavior as + * {@link #unregisterNetworkCallback(android.app.PendingIntent)} with respect to + * releasing network resources and disconnecting. + * + * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the + * PendingIntent passed to + * {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the + * corresponding NetworkRequest you'd like to remove. Cannot be null. + */ + public void releaseNetworkRequest(@NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.releasePendingNetworkRequest(operation); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static void checkPendingIntentNotNull(PendingIntent intent) { + Preconditions.checkNotNull(intent, "PendingIntent cannot be null."); + } + + private static void checkCallbackNotNull(NetworkCallback callback) { + Preconditions.checkNotNull(callback, "null NetworkCallback"); + } + + private static void checkTimeout(int timeoutMs) { + Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive."); + } + + /** + * Registers to receive notifications about all networks which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * The callback is invoked on the default internal Handler. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback) { + registerNetworkCallback(request, networkCallback, getDefaultHandler()); + } + + /** + * Registers to receive notifications about all networks which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler); + } + + /** + * Registers a PendingIntent to be sent when a network is available which satisfies the given + * {@link NetworkRequest}. + * + * This function behaves identically to the version that takes a NetworkCallback, but instead + * of {@link NetworkCallback} a {@link PendingIntent} is used. This means + * the request may outlive the calling application and get called back when a suitable + * network is found. + *

+ * The operation is an Intent broadcast that goes to a broadcast receiver that + * you registered with {@link Context#registerReceiver} or through the + * <receiver> tag in an AndroidManifest.xml file + *

+ * The operation Intent is delivered with two extras, a {@link Network} typed + * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest} + * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing + * the original requests parameters. + *

+ * If there is already a request for this Intent registered (with the equality of + * two Intents defined by {@link Intent#filterEquals}), then it will be removed and + * replaced by this one, effectively releasing the previous {@link NetworkRequest}. + *

+ * The request may be released normally by calling + * {@link #unregisterNetworkCallback(android.app.PendingIntent)}. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with {@link #unregisterNetworkCallback(PendingIntent)} + * or {@link #releaseNetworkRequest(PendingIntent)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param operation Action to perform when the network is available (corresponds + * to the {@link NetworkCallback#onAvailable} call. Typically + * comes from {@link PendingIntent#getBroadcast}. Cannot be null. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerNetworkCallback(@NonNull NetworkRequest request, + @NonNull PendingIntent operation) { + printStackTrace(); + checkPendingIntentNotNull(operation); + try { + mService.pendingListenForNetwork( + request.networkCapabilities, operation, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw convertServiceException(e); + } + } + + /** + * Registers to receive notifications about changes in the system default network. The callbacks + * will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * system default network changes. + * The callback is invoked on the default internal Handler. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback) { + registerDefaultNetworkCallback(networkCallback, getDefaultHandler()); + } + + /** + * Registers to receive notifications about changes in the system default network. The callbacks + * will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * system default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, + @NonNull Handler handler) { + // This works because if the NetworkCapabilities are null, + // ConnectivityService takes them from the default request. + // + // Since the capabilities are exactly the same as the default request's + // capabilities, this request is guaranteed, at all times, to be + // satisfied by the same network, if any, that satisfies the default + // request, i.e., the system default network. + CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, + TRACK_DEFAULT, TYPE_NONE, cbHandler); + } + + /** + * Requests bandwidth update for a given {@link Network} and returns whether the update request + * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying + * network connection for updated bandwidth information. The caller will be notified via + * {@link ConnectivityManager.NetworkCallback} if there is an update. Notice that this + * method assumes that the caller has previously called + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} to listen for network + * changes. + * + * @param network {@link Network} specifying which network you're interested. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean requestBandwidthUpdate(@NonNull Network network) { + try { + return mService.requestBandwidthUpdate(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a {@code NetworkCallback} and possibly releases networks originating from + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and + * {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} calls. + * If the given {@code NetworkCallback} had previously been used with + * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request + * will be disconnected. + * + * Notifications that would have triggered that {@code NetworkCallback} will immediately stop + * triggering it as soon as this call returns. + * + * @param networkCallback The {@link NetworkCallback} used when making the request. + */ + public void unregisterNetworkCallback(@NonNull NetworkCallback networkCallback) { + printStackTrace(); + checkCallbackNotNull(networkCallback); + final List reqs = new ArrayList<>(); + // Find all requests associated to this callback and stop callback triggers immediately. + // Callback is reusable immediately. http://b/20701525, http://b/35921499. + synchronized (sCallbacks) { + Preconditions.checkArgument(networkCallback.networkRequest != null, + "NetworkCallback was not registered"); + if (networkCallback.networkRequest == ALREADY_UNREGISTERED) { + Log.d(TAG, "NetworkCallback was already unregistered"); + return; + } + for (Map.Entry e : sCallbacks.entrySet()) { + if (e.getValue() == networkCallback) { + reqs.add(e.getKey()); + } + } + // TODO: throw exception if callback was registered more than once (http://b/20701525). + for (NetworkRequest r : reqs) { + try { + mService.releaseNetworkRequest(r); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + // Only remove mapping if rpc was successful. + sCallbacks.remove(r); + } + networkCallback.networkRequest = ALREADY_UNREGISTERED; + } + } + + /** + * Unregisters a callback previously registered via + * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}. + * + * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the + * PendingIntent passed to + * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}. + * Cannot be null. + */ + public void unregisterNetworkCallback(@NonNull PendingIntent operation) { + releaseNetworkRequest(operation); + } + + /** + * Informs the system whether it should switch to {@code network} regardless of whether it is + * validated or not. If {@code accept} is true, and the network was explicitly selected by the + * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become + * the system default network regardless of any other network that's currently connected. If + * {@code always} is true, then the choice is remembered, so that the next time the user + * connects to this network, the system will switch to it. + * + * @param network The network to accept. + * @param accept Whether to accept the network even if unvalidated. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { + try { + mService.setAcceptUnvalidated(network, accept, always); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Informs the system whether it should consider the network as validated even if it only has + * partial connectivity. If {@code accept} is true, then the network will be considered as + * validated even if connectivity is only partial. If {@code always} is true, then the choice + * is remembered, so that the next time the user connects to this network, the system will + * switch to it. + * + * @param network The network to accept. + * @param accept Whether to consider the network as validated even if it has partial + * connectivity. + * @param always Whether to remember this choice in the future. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) { + try { + mService.setAcceptPartialConnectivity(network, accept, always); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Informs the system to penalize {@code network}'s score when it becomes unvalidated. This is + * only meaningful if the system is configured not to penalize such networks, e.g., if the + * {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code + * NETWORK_AVOID_BAD_WIFI setting is unset}. + * + * @param network The network to accept. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void setAvoidUnvalidated(Network network) { + try { + mService.setAvoidUnvalidated(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests that the system open the captive portal app on the specified network. + * + * @param network The network to log into. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void startCaptivePortalApp(Network network) { + try { + mService.startCaptivePortalApp(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests that the system open the captive portal app with the specified extras. + * + *

This endpoint is exclusively for use by the NetworkStack and is protected by the + * corresponding permission. + * @param network Network on which the captive portal was detected. + * @param appExtras Extras to include in the app start intent. + * @hide + */ + @SystemApi + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void startCaptivePortalApp(@NonNull Network network, @NonNull Bundle appExtras) { + try { + mService.startCaptivePortalAppInternal(network, appExtras); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Determine whether the device is configured to avoid bad wifi. + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public boolean shouldAvoidBadWifi() { + try { + return mService.shouldAvoidBadWifi(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * It is acceptable to briefly use multipath data to provide seamless connectivity for + * time-sensitive user-facing operations when the system default network is temporarily + * unresponsive. The amount of data should be limited (less than one megabyte for every call to + * this method), and the operation should be infrequent to ensure that data usage is limited. + * + * An example of such an operation might be a time-sensitive foreground activity, such as a + * voice command, that the user is performing while walking out of range of a Wi-Fi network. + */ + public static final int MULTIPATH_PREFERENCE_HANDOVER = 1 << 0; + + /** + * It is acceptable to use small amounts of multipath data on an ongoing basis to provide + * a backup channel for traffic that is primarily going over another network. + * + * An example might be maintaining backup connections to peers or servers for the purpose of + * fast fallback if the default network is temporarily unresponsive or disconnects. The traffic + * on backup paths should be negligible compared to the traffic on the main path. + */ + public static final int MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1; + + /** + * It is acceptable to use metered data to improve network latency and performance. + */ + public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2; + + /** + * Return value to use for unmetered networks. On such networks we currently set all the flags + * to true. + * @hide + */ + public static final int MULTIPATH_PREFERENCE_UNMETERED = + MULTIPATH_PREFERENCE_HANDOVER | + MULTIPATH_PREFERENCE_RELIABILITY | + MULTIPATH_PREFERENCE_PERFORMANCE; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = { + MULTIPATH_PREFERENCE_HANDOVER, + MULTIPATH_PREFERENCE_RELIABILITY, + MULTIPATH_PREFERENCE_PERFORMANCE, + }) + public @interface MultipathPreference { + } + + /** + * Provides a hint to the calling application on whether it is desirable to use the + * multinetwork APIs (e.g., {@link Network#openConnection}, {@link Network#bindSocket}, etc.) + * for multipath data transfer on this network when it is not the system default network. + * Applications desiring to use multipath network protocols should call this method before + * each such operation. + * + * @param network The network on which the application desires to use multipath data. + * If {@code null}, this method will return the a preference that will generally + * apply to metered networks. + * @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public @MultipathPreference int getMultipathPreference(@Nullable Network network) { + try { + return mService.getMultipathPreference(network); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Resets all connectivity manager settings back to factory defaults. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void factoryReset() { + try { + mService.factoryReset(); + mTetheringManager.stopAllTethering(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code bindProcessToNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + */ + public boolean bindProcessToNetwork(@Nullable Network network) { + // Forcing callers to call through non-static function ensures ConnectivityManager + // instantiated. + return setProcessDefaultNetwork(network); + } + + /** + * Binds the current process to {@code network}. All Sockets created in the future + * (and not explicitly bound via a bound SocketFactory from + * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to + * {@code network}. All host name resolutions will be limited to {@code network} as well. + * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to + * work and all host name resolutions will fail. This is by design so an application doesn't + * accidentally use Sockets it thinks are still bound to a particular {@link Network}. + * To clear binding pass {@code null} for {@code network}. Using individually bound + * Sockets created by Network.getSocketFactory().createSocket() and + * performing network-specific host name resolutions via + * {@link Network#getAllByName Network.getAllByName} is preferred to calling + * {@code setProcessDefaultNetwork}. + * + * @param network The {@link Network} to bind the current process to, or {@code null} to clear + * the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @deprecated This function can throw {@link IllegalStateException}. Use + * {@link #bindProcessToNetwork} instead. {@code bindProcessToNetwork} + * is a direct replacement. + */ + @Deprecated + public static boolean setProcessDefaultNetwork(@Nullable Network network) { + int netId = (network == null) ? NETID_UNSET : network.netId; + boolean isSameNetId = (netId == NetworkUtils.getBoundNetworkForProcess()); + + if (netId != NETID_UNSET) { + netId = network.getNetIdForResolv(); + } + + if (!NetworkUtils.bindProcessToNetwork(netId)) { + return false; + } + + if (!isSameNetId) { + // Set HTTP proxy system properties to match network. + // TODO: Deprecate this static method and replace it with a non-static version. + try { + Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy()); + } catch (SecurityException e) { + // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy. + Log.e(TAG, "Can't set proxy properties", e); + } + // Must flush DNS cache as new network may have different DNS resolutions. + InetAddress.clearDnsCache(); + // Must flush socket pool as idle sockets will be bound to previous network and may + // cause subsequent fetches to be performed on old network. + NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged(); + } + + return true; + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + */ + @Nullable + public Network getBoundNetworkForProcess() { + // Forcing callers to call thru non-static function ensures ConnectivityManager + // instantiated. + return getProcessDefaultNetwork(); + } + + /** + * Returns the {@link Network} currently bound to this process via + * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound. + * + * @return {@code Network} to which this process is bound, or {@code null}. + * @deprecated Using this function can lead to other functions throwing + * {@link IllegalStateException}. Use {@link #getBoundNetworkForProcess} instead. + * {@code getBoundNetworkForProcess} is a direct replacement. + */ + @Deprecated + @Nullable + public static Network getProcessDefaultNetwork() { + int netId = NetworkUtils.getBoundNetworkForProcess(); + if (netId == NETID_UNSET) return null; + return new Network(netId); + } + + private void unsupportedStartingFrom(int version) { + if (Process.myUid() == Process.SYSTEM_UID) { + // The getApplicationInfo() call we make below is not supported in system context. Let + // the call through here, and rely on the fact that ConnectivityService will refuse to + // allow the system to use these APIs anyway. + return; + } + + if (mContext.getApplicationInfo().targetSdkVersion >= version) { + throw new UnsupportedOperationException( + "This method is not supported in target SDK version " + version + " and above"); + } + } + + // Checks whether the calling app can use the legacy routing API (startUsingNetworkFeature, + // stopUsingNetworkFeature, requestRouteToHost), and if not throw UnsupportedOperationException. + // TODO: convert the existing system users (Tethering, GnssLocationProvider) to the new APIs and + // remove these exemptions. Note that this check is not secure, and apps can still access these + // functions by accessing ConnectivityService directly. However, it should be clear that doing + // so is unsupported and may break in the future. http://b/22728205 + private void checkLegacyRoutingApiAccess() { + unsupportedStartingFrom(VERSION_CODES.M); + } + + /** + * Binds host resolutions performed by this process to {@code network}. + * {@link #bindProcessToNetwork} takes precedence over this setting. + * + * @param network The {@link Network} to bind host resolutions from the current process to, or + * {@code null} to clear the current binding. + * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid. + * @hide + * @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}. + */ + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static boolean setProcessDefaultNetworkForHostResolution(Network network) { + return NetworkUtils.bindProcessToNetworkForHostResolution( + (network == null) ? NETID_UNSET : network.getNetIdForResolv()); + } + + /** + * Device is not restricting metered network activity while application is running on + * background. + */ + public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; + + /** + * Device is restricting metered network activity while application is running on background, + * but application is allowed to bypass it. + *

+ * In this state, application should take action to mitigate metered network access. + * For example, a music streaming application should switch to a low-bandwidth bitrate. + */ + public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; + + /** + * Device is restricting metered network activity while application is running on background. + *

+ * In this state, application should not try to use the network while running on background, + * because it would be denied. + */ + public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; + + /** + * A change in the background metered network activity restriction has occurred. + *

+ * Applications should call {@link #getRestrictBackgroundStatus()} to check if the restriction + * applies to them. + *

+ * This is only sent to registered receivers, not manifest receivers. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = + "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, value = { + RESTRICT_BACKGROUND_STATUS_DISABLED, + RESTRICT_BACKGROUND_STATUS_WHITELISTED, + RESTRICT_BACKGROUND_STATUS_ENABLED, + }) + public @interface RestrictBackgroundStatus { + } + + private INetworkPolicyManager getNetworkPolicyManager() { + synchronized (this) { + if (mNPManager != null) { + return mNPManager; + } + mNPManager = INetworkPolicyManager.Stub.asInterface(ServiceManager + .getService(Context.NETWORK_POLICY_SERVICE)); + return mNPManager; + } + } + + /** + * Determines if the calling application is subject to metered network restrictions while + * running on background. + * + * @return {@link #RESTRICT_BACKGROUND_STATUS_DISABLED}, + * {@link #RESTRICT_BACKGROUND_STATUS_ENABLED}, + * or {@link #RESTRICT_BACKGROUND_STATUS_WHITELISTED} + */ + public @RestrictBackgroundStatus int getRestrictBackgroundStatus() { + try { + return getNetworkPolicyManager().getRestrictBackgroundByCaller(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * The network watchlist is a list of domains and IP addresses that are associated with + * potentially harmful apps. This method returns the SHA-256 of the watchlist config file + * currently used by the system for validation purposes. + * + * @return Hash of network watchlist config file. Null if config does not exist. + */ + @Nullable + public byte[] getNetworkWatchlistConfigHash() { + try { + return mService.getNetworkWatchlistConfigHash(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get watchlist config hash"); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the {@code uid} of the owner of a network connection. + * + * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and {@code + * IPPROTO_UDP} currently supported. + * @param local The local {@link InetSocketAddress} of a connection. + * @param remote The remote {@link InetSocketAddress} of a connection. + * @return {@code uid} if the connection is found and the app has permission to observe it + * (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link + * android.os.Process#INVALID_UID} if the connection is not found. + * @throws {@link SecurityException} if the caller is not the active VpnService for the current + * user. + * @throws {@link IllegalArgumentException} if an unsupported protocol is requested. + */ + public int getConnectionOwnerUid( + int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) { + ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote); + try { + return mService.getConnectionOwnerUid(connectionInfo); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void printStackTrace() { + if (DEBUG) { + final StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); + final StringBuffer sb = new StringBuffer(); + for (int i = 3; i < callStack.length; i++) { + final String stackTrace = callStack[i].toString(); + if (stackTrace == null || stackTrace.contains("android.os")) { + break; + } + sb.append(" [").append(stackTrace).append("]"); + } + Log.d(TAG, "StackLog:" + sb.toString()); + } + } + + /** + * Simulates a Data Stall for the specified Network. + * + *

This method should only be used for tests. + * + *

The caller must be the owner of the specified Network. + * + * @param detectionMethod The detection method used to identify the Data Stall. + * @param timestampMillis The timestamp at which the stall 'occurred', in milliseconds. + * @param network The Network for which a Data Stall is being simluated. + * @param extras The PersistableBundle of extras included in the Data Stall notification. + * @throws SecurityException if the caller is not the owner of the given network. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_TEST_NETWORKS, + android.Manifest.permission.NETWORK_STACK}) + public void simulateDataStall(int detectionMethod, long timestampMillis, + @NonNull Network network, @NonNull PersistableBundle extras) { + try { + mService.simulateDataStall(detectionMethod, timestampMillis, network, extras); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + private void setOemNetworkPreference(@NonNull OemNetworkPreferences preference) { + Log.d(TAG, "setOemNetworkPreference called with preference: " + + preference.toString()); + } + + @NonNull + private final List mQosCallbackConnections = new ArrayList<>(); + + /** + * Registers a {@link QosSocketInfo} with an associated {@link QosCallback}. The callback will + * receive available QoS events related to the {@link Network} and local ip + port + * specified within socketInfo. + *

+ * The same {@link QosCallback} must be unregistered before being registered a second time, + * otherwise {@link QosCallbackRegistrationException} is thrown. + *

+ * This API does not, in itself, require any permission if called with a network that is not + * restricted. However, the underlying implementation currently only supports the IMS network, + * which is always restricted. That means non-preinstalled callers can't possibly find this API + * useful, because they'd never be called back on networks that they would have access to. + * + * @throws SecurityException if {@link QosSocketInfo#getNetwork()} is restricted and the app is + * missing CONNECTIVITY_USE_RESTRICTED_NETWORKS permission. + * @throws QosCallback.QosCallbackRegistrationException if qosCallback is already registered. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * Exceptions after the time of registration is passed through + * {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}. + * + * @param socketInfo the socket information used to match QoS events + * @param callback receives qos events that satisfy socketInfo + * @param executor The executor on which the callback will be invoked. The provided + * {@link Executor} must run callback sequentially, otherwise the order of + * callbacks cannot be guaranteed. + * + * @hide + */ + @SystemApi + public void registerQosCallback(@NonNull final QosSocketInfo socketInfo, + @NonNull final QosCallback callback, + @CallbackExecutor @NonNull final Executor executor) { + Objects.requireNonNull(socketInfo, "socketInfo must be non-null"); + Objects.requireNonNull(callback, "callback must be non-null"); + Objects.requireNonNull(executor, "executor must be non-null"); + + try { + synchronized (mQosCallbackConnections) { + if (getQosCallbackConnection(callback) == null) { + final QosCallbackConnection connection = + new QosCallbackConnection(this, callback, executor); + mQosCallbackConnections.add(connection); + mService.registerQosSocketCallback(socketInfo, connection); + } else { + Log.e(TAG, "registerQosCallback: Callback already registered"); + throw new QosCallbackRegistrationException(); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + + // The same unregister method method is called for consistency even though nothing + // will be sent to the ConnectivityService since the callback was never successfully + // registered. + unregisterQosCallback(callback); + e.rethrowFromSystemServer(); + } catch (final ServiceSpecificException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + unregisterQosCallback(callback); + throw convertServiceException(e); + } + } + + /** + * Unregisters the given {@link QosCallback}. The {@link QosCallback} will no longer receive + * events once unregistered and can be registered a second time. + *

+ * If the {@link QosCallback} does not have an active registration, it is a no-op. + * + * @param callback the callback being unregistered + * + * @hide + */ + @SystemApi + public void unregisterQosCallback(@NonNull final QosCallback callback) { + Objects.requireNonNull(callback, "The callback must be non-null"); + try { + synchronized (mQosCallbackConnections) { + final QosCallbackConnection connection = getQosCallbackConnection(callback); + if (connection != null) { + connection.stopReceivingMessages(); + mService.unregisterQosCallback(connection); + mQosCallbackConnections.remove(connection); + } else { + Log.d(TAG, "unregisterQosCallback: Callback not registered"); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "unregisterQosCallback: Error while unregistering ", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Gets the connection related to the callback. + * + * @param callback the callback to look up + * @return the related connection + */ + @Nullable + private QosCallbackConnection getQosCallbackConnection(final QosCallback callback) { + for (final QosCallbackConnection connection : mQosCallbackConnections) { + // Checking by reference here is intentional + if (connection.getCallback() == callback) { + return connection; + } + } + return null; + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, but + * does not cause any networks to retain the NET_CAPABILITY_FOREGROUND capability. This can + * be used to request that the system provide a network without causing the network to be + * in the foreground. + * + *

This method will attempt to find the best network that matches the passed + * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the + * criteria. The platform will evaluate which network is the best at its own discretion. + * Throughput, latency, cost per byte, policy, user preference and other considerations + * may be factored in the decision of what is considered the best network. + * + *

As long as this request is outstanding, the platform will try to maintain the best network + * matching this request, while always attempting to match the request to a better network if + * possible. If a better match is found, the platform will switch this request to the now-best + * network and inform the app of the newly best network by invoking + * {@link NetworkCallback#onAvailable(Network)} on the provided callback. Note that the platform + * will not try to maintain any other network than the best one currently matching the request: + * a network not matching any network request may be disconnected at any time. + * + *

For example, an application could use this method to obtain a connected cellular network + * even if the device currently has a data connection over Ethernet. This may cause the cellular + * radio to consume additional power. Or, an application could inform the system that it wants + * a network supporting sending MMSes and have the system let it know about the currently best + * MMS-supporting network through the provided {@link NetworkCallback}. + * + *

The status of the request can be followed by listening to the various callbacks described + * in {@link NetworkCallback}. The {@link Network} object passed to the callback methods can be + * used to direct traffic to the network (although accessing some networks may be subject to + * holding specific permissions). Callers will learn about the specific characteristics of the + * network through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} and + * {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)}. The methods of the + * provided {@link NetworkCallback} will only be invoked due to changes in the best network + * matching the request at any given time; therefore when a better network matching the request + * becomes available, the {@link NetworkCallback#onAvailable(Network)} method is called + * with the new network after which no further updates are given about the previously-best + * network, unless it becomes the best again at some later time. All callbacks are invoked + * in order on the same thread, which by default is a thread created by the framework running + * in the app. + * + *

This{@link NetworkRequest} will live until released via + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits, at + * which point the system may let go of the network at any time. + * + *

It is presently unsupported to request a network with mutable + * {@link NetworkCapabilities} such as + * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL} + * as these {@code NetworkCapabilities} represent states that a particular + * network may never attain, and whether a network will attain these states + * is unknown prior to bringing up the network so the framework does not + * know how to go about satisfying a request with these capabilities. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #registerNetworkCallback} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param request {@link NetworkRequest} describing this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * If null, the callback is invoked on the default internal Handler. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. + * @throws SecurityException if missing the appropriate permissions. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint("ExecutorRegistration") + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void requestBackgroundNetwork(@NonNull NetworkRequest request, + @Nullable Handler handler, @NonNull NetworkCallback networkCallback) { + final NetworkCapabilities nc = request.networkCapabilities; + sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, + TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler)); + } +} diff --git a/framework/src/android/net/ConnectivityMetricsEvent.aidl b/framework/src/android/net/ConnectivityMetricsEvent.aidl new file mode 100644 index 0000000000..1c541dc4c8 --- /dev/null +++ b/framework/src/android/net/ConnectivityMetricsEvent.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** {@hide} */ +parcelable ConnectivityMetricsEvent; diff --git a/framework/src/android/net/ConnectivityThread.java b/framework/src/android/net/ConnectivityThread.java new file mode 100644 index 0000000000..0b218e738b --- /dev/null +++ b/framework/src/android/net/ConnectivityThread.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.os.HandlerThread; +import android.os.Looper; + +/** + * Shared singleton connectivity thread for the system. This is a thread for + * connectivity operations such as AsyncChannel connections to system services. + * Various connectivity manager objects can use this singleton as a common + * resource for their handlers instead of creating separate threads of their own. + * @hide + */ +public final class ConnectivityThread extends HandlerThread { + + // A class implementing the lazy holder idiom: the unique static instance + // of ConnectivityThread is instantiated in a thread-safe way (guaranteed by + // the language specs) the first time that Singleton is referenced in get() + // or getInstanceLooper(). + private static class Singleton { + private static final ConnectivityThread INSTANCE = createInstance(); + } + + private ConnectivityThread() { + super("ConnectivityThread"); + } + + private static ConnectivityThread createInstance() { + ConnectivityThread t = new ConnectivityThread(); + t.start(); + return t; + } + + public static ConnectivityThread get() { + return Singleton.INSTANCE; + } + + public static Looper getInstanceLooper() { + return Singleton.INSTANCE.getLooper(); + } +} diff --git a/framework/src/android/net/DhcpInfo.aidl b/framework/src/android/net/DhcpInfo.aidl new file mode 100644 index 0000000000..29cd21fe76 --- /dev/null +++ b/framework/src/android/net/DhcpInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, 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; + +parcelable DhcpInfo; diff --git a/framework/src/android/net/DhcpInfo.java b/framework/src/android/net/DhcpInfo.java new file mode 100644 index 0000000000..912df67a0b --- /dev/null +++ b/framework/src/android/net/DhcpInfo.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008 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; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A simple object for retrieving the results of a DHCP request. + */ +public class DhcpInfo implements Parcelable { + public int ipAddress; + public int gateway; + public int netmask; + public int dns1; + public int dns2; + public int serverAddress; + + public int leaseDuration; + + public DhcpInfo() { + super(); + } + + /** copy constructor {@hide} */ + public DhcpInfo(DhcpInfo source) { + if (source != null) { + ipAddress = source.ipAddress; + gateway = source.gateway; + netmask = source.netmask; + dns1 = source.dns1; + dns2 = source.dns2; + serverAddress = source.serverAddress; + leaseDuration = source.leaseDuration; + } + } + + public String toString() { + StringBuffer str = new StringBuffer(); + + str.append("ipaddr "); putAddress(str, ipAddress); + str.append(" gateway "); putAddress(str, gateway); + str.append(" netmask "); putAddress(str, netmask); + str.append(" dns1 "); putAddress(str, dns1); + str.append(" dns2 "); putAddress(str, dns2); + str.append(" DHCP server "); putAddress(str, serverAddress); + str.append(" lease ").append(leaseDuration).append(" seconds"); + + return str.toString(); + } + + private static void putAddress(StringBuffer buf, int addr) { + buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress()); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(ipAddress); + dest.writeInt(gateway); + dest.writeInt(netmask); + dest.writeInt(dns1); + dest.writeInt(dns2); + dest.writeInt(serverAddress); + dest.writeInt(leaseDuration); + } + + /** Implement the Parcelable interface */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public DhcpInfo createFromParcel(Parcel in) { + DhcpInfo info = new DhcpInfo(); + info.ipAddress = in.readInt(); + info.gateway = in.readInt(); + info.netmask = in.readInt(); + info.dns1 = in.readInt(); + info.dns2 = in.readInt(); + info.serverAddress = in.readInt(); + info.leaseDuration = in.readInt(); + return info; + } + + public DhcpInfo[] newArray(int size) { + return new DhcpInfo[size]; + } + }; +} diff --git a/framework/src/android/net/DnsResolver.java b/framework/src/android/net/DnsResolver.java new file mode 100644 index 0000000000..3f7660f570 --- /dev/null +++ b/framework/src/android/net/DnsResolver.java @@ -0,0 +1,577 @@ +/* + * 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; + +import static android.net.NetworkUtils.getDnsNetwork; +import static android.net.NetworkUtils.resNetworkCancel; +import static android.net.NetworkUtils.resNetworkQuery; +import static android.net.NetworkUtils.resNetworkResult; +import static android.net.NetworkUtils.resNetworkSend; +import static android.net.util.DnsUtils.haveIpv4; +import static android.net.util.DnsUtils.haveIpv6; +import static android.net.util.DnsUtils.rfc6724Sort; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR; +import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.system.OsConstants.ENONET; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.CancellationSignal; +import android.os.Looper; +import android.os.MessageQueue; +import android.system.ErrnoException; +import android.util.Log; + +import com.android.net.module.util.DnsPacket; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Dns resolver class for asynchronous dns querying + * + * Note that if a client sends a query with more than 1 record in the question section but + * the remote dns server does not support this, it may not respond at all, leading to a timeout. + * + */ +public final class DnsResolver { + private static final String TAG = "DnsResolver"; + private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR; + private static final int MAXPACKET = 8 * 1024; + private static final int SLEEP_TIME_MS = 2; + + @IntDef(prefix = { "CLASS_" }, value = { + CLASS_IN + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryClass {} + public static final int CLASS_IN = 1; + + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_A, + TYPE_AAAA + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryType {} + public static final int TYPE_A = 1; + public static final int TYPE_AAAA = 28; + + @IntDef(prefix = { "FLAG_" }, value = { + FLAG_EMPTY, + FLAG_NO_RETRY, + FLAG_NO_CACHE_STORE, + FLAG_NO_CACHE_LOOKUP + }) + @Retention(RetentionPolicy.SOURCE) + @interface QueryFlag {} + public static final int FLAG_EMPTY = 0; + public static final int FLAG_NO_RETRY = 1 << 0; + public static final int FLAG_NO_CACHE_STORE = 1 << 1; + public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2; + + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_PARSE, + ERROR_SYSTEM + }) + @Retention(RetentionPolicy.SOURCE) + @interface DnsError {} + /** + * Indicates that there was an error parsing the response the query. + * The cause of this error is available via getCause() and is a {@link ParseException}. + */ + public static final int ERROR_PARSE = 0; + /** + * Indicates that there was an error sending the query. + * The cause of this error is available via getCause() and is an ErrnoException. + */ + public static final int ERROR_SYSTEM = 1; + + private static final int NETID_UNSET = 0; + + private static final DnsResolver sInstance = new DnsResolver(); + + /** + * Get instance for DnsResolver + */ + public static @NonNull DnsResolver getInstance() { + return sInstance; + } + + private DnsResolver() {} + + /** + * Base interface for answer callbacks + * + * @param The type of the answer + */ + public interface Callback { + /** + * Success response to + * {@link android.net.DnsResolver#query query()} or + * {@link android.net.DnsResolver#rawQuery rawQuery()}. + * + * Invoked when the answer to a query was successfully parsed. + * + * @param answer answer to the query. + * @param rcode The response code in the DNS response. + * + * {@see android.net.DnsResolver#query query()} + */ + void onAnswer(@NonNull T answer, int rcode); + /** + * Error response to + * {@link android.net.DnsResolver#query query()} or + * {@link android.net.DnsResolver#rawQuery rawQuery()}. + * + * Invoked when there is no valid answer to + * {@link android.net.DnsResolver#query query()} + * {@link android.net.DnsResolver#rawQuery rawQuery()}. + * + * @param error a {@link DnsException} object with additional + * detail regarding the failure + */ + void onError(@NonNull DnsException error); + } + + /** + * Class to represent DNS error + */ + public static class DnsException extends Exception { + /** + * DNS error code as one of the ERROR_* constants + */ + @DnsError public final int code; + + DnsException(@DnsError int code, @Nullable Throwable cause) { + super(cause); + this.code = code; + } + } + + /** + * Send a raw DNS query. + * The answer will be provided asynchronously through the provided {@link Callback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param query blob message to query + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback a {@link Callback} which will be called to notify the caller + * of the result of dns query. + */ + public void rawQuery(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull Callback callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + final Object lock = new Object(); + final FileDescriptor queryfd; + try { + queryfd = resNetworkSend((network != null) + ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + + synchronized (lock) { + registerFDListener(executor, queryfd, callback, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } + } + + /** + * Send a DNS query with the specified name, class and query type. + * The answer will be provided asynchronously through the provided {@link Callback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param domain domain name to query + * @param nsClass dns class as one of the CLASS_* constants + * @param nsType dns resource record (RR) type as one of the TYPE_* constants + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback a {@link Callback} which will be called to notify the caller + * of the result of dns query. + */ + public void rawQuery(@Nullable Network network, @NonNull String domain, + @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull Callback callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + final Object lock = new Object(); + final FileDescriptor queryfd; + try { + queryfd = resNetworkQuery((network != null) + ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + synchronized (lock) { + registerFDListener(executor, queryfd, callback, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } + } + + private class InetAddressAnswerAccumulator implements Callback { + private final List mAllAnswers; + private final Network mNetwork; + private int mRcode; + private DnsException mDnsException; + private final Callback> mUserCallback; + private final int mTargetAnswerCount; + private int mReceivedAnswerCount = 0; + + InetAddressAnswerAccumulator(@NonNull Network network, int size, + @NonNull Callback> callback) { + mNetwork = network; + mTargetAnswerCount = size; + mAllAnswers = new ArrayList<>(); + mUserCallback = callback; + } + + private boolean maybeReportError() { + if (mRcode != 0) { + mUserCallback.onAnswer(mAllAnswers, mRcode); + return true; + } + if (mDnsException != null) { + mUserCallback.onError(mDnsException); + return true; + } + return false; + } + + private void maybeReportAnswer() { + if (++mReceivedAnswerCount != mTargetAnswerCount) return; + if (mAllAnswers.isEmpty() && maybeReportError()) return; + mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode); + } + + @Override + public void onAnswer(@NonNull byte[] answer, int rcode) { + // If at least one query succeeded, return an rcode of 0. + // Otherwise, arbitrarily return the first rcode received. + if (mReceivedAnswerCount == 0 || rcode == 0) { + mRcode = rcode; + } + try { + mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses()); + } catch (DnsPacket.ParseException e) { + // Convert the com.android.net.module.util.DnsPacket.ParseException to an + // android.net.ParseException. This is the type that was used in Q and is implied + // by the public documentation of ERROR_PARSE. + // + // DnsPacket cannot throw android.net.ParseException directly because it's @hide. + ParseException pe = new ParseException(e.reason, e.getCause()); + pe.setStackTrace(e.getStackTrace()); + mDnsException = new DnsException(ERROR_PARSE, pe); + } + maybeReportAnswer(); + } + + @Override + public void onError(@NonNull DnsException error) { + mDnsException = error; + maybeReportAnswer(); + } + } + + /** + * Send a DNS query with the specified name on a network with both IPv4 and IPv6, + * get back a set of InetAddresses with rfc6724 sorting style asynchronously. + * + * This method will examine the connection ability on given network, and query IPv4 + * and IPv6 if connection is available. + * + * If at least one query succeeded with valid answer, rcode will be 0 + * + * The answer will be provided asynchronously through the provided {@link Callback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param domain domain name to query + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback a {@link Callback} which will be called to notify the + * caller of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull Callback> callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + final Object lock = new Object(); + final Network queryNetwork; + try { + queryNetwork = (network != null) ? network : getDnsNetwork(); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + final boolean queryIpv6 = haveIpv6(queryNetwork); + final boolean queryIpv4 = haveIpv4(queryNetwork); + + // This can only happen if queryIpv4 and queryIpv6 are both false. + // This almost certainly means that queryNetwork does not exist or no longer exists. + if (!queryIpv6 && !queryIpv4) { + executor.execute(() -> callback.onError( + new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET)))); + return; + } + + final FileDescriptor v4fd; + final FileDescriptor v6fd; + + int queryCount = 0; + + if (queryIpv6) { + try { + v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, + TYPE_AAAA, flags); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + queryCount++; + } else v6fd = null; + + // Avoiding gateways drop packets if queries are sent too close together + try { + Thread.sleep(SLEEP_TIME_MS); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + + if (queryIpv4) { + try { + v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A, + flags); + } catch (ErrnoException e) { + if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid. + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + queryCount++; + } else v4fd = null; + + final InetAddressAnswerAccumulator accumulator = + new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback); + + synchronized (lock) { + if (queryIpv6) { + registerFDListener(executor, v6fd, accumulator, cancellationSignal, lock); + } + if (queryIpv4) { + registerFDListener(executor, v4fd, accumulator, cancellationSignal, lock); + } + if (cancellationSignal == null) return; + cancellationSignal.setOnCancelListener(() -> { + synchronized (lock) { + if (queryIpv4) cancelQuery(v4fd); + if (queryIpv6) cancelQuery(v6fd); + } + }); + } + } + + /** + * Send a DNS query with the specified name and query type, get back a set of + * InetAddresses with rfc6724 sorting style asynchronously. + * + * The answer will be provided asynchronously through the provided {@link Callback}. + * + * @param network {@link Network} specifying which network to query on. + * {@code null} for query on default network. + * @param domain domain name to query + * @param nsType dns resource record (RR) type as one of the TYPE_* constants + * @param flags flags as a combination of the FLAGS_* constants + * @param executor The {@link Executor} that the callback should be executed on. + * @param cancellationSignal used by the caller to signal if the query should be + * cancelled. May be {@code null}. + * @param callback a {@link Callback} which will be called to notify the caller + * of the result of dns query. + */ + public void query(@Nullable Network network, @NonNull String domain, + @QueryType int nsType, @QueryFlag int flags, + @NonNull @CallbackExecutor Executor executor, + @Nullable CancellationSignal cancellationSignal, + @NonNull Callback> callback) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + final Object lock = new Object(); + final FileDescriptor queryfd; + final Network queryNetwork; + try { + queryNetwork = (network != null) ? network : getDnsNetwork(); + queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType, + flags); + } catch (ErrnoException e) { + executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e))); + return; + } + final InetAddressAnswerAccumulator accumulator = + new InetAddressAnswerAccumulator(queryNetwork, 1, callback); + synchronized (lock) { + registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock); + if (cancellationSignal == null) return; + addCancellationSignal(cancellationSignal, queryfd, lock); + } + } + + /** + * Class to retrieve DNS response + * + * @hide + */ + public static final class DnsResponse { + public final @NonNull byte[] answerbuf; + public final int rcode; + public DnsResponse(@NonNull byte[] answerbuf, int rcode) { + this.answerbuf = answerbuf; + this.rcode = rcode; + } + } + + private void registerFDListener(@NonNull Executor executor, + @NonNull FileDescriptor queryfd, @NonNull Callback answerCallback, + @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) { + final MessageQueue mainThreadMessageQueue = Looper.getMainLooper().getQueue(); + mainThreadMessageQueue.addOnFileDescriptorEventListener( + queryfd, + FD_EVENTS, + (fd, events) -> { + // b/134310704 + // Unregister fd event listener before resNetworkResult is called to prevent + // race condition caused by fd reused. + // For example when querying v4 and v6, it's possible that the first query ends + // and the fd is closed before the second request starts, which might return + // the same fd for the second request. By that time, the looper must have + // unregistered the fd, otherwise another event listener can't be registered. + mainThreadMessageQueue.removeOnFileDescriptorEventListener(fd); + + executor.execute(() -> { + DnsResponse resp = null; + ErrnoException exception = null; + synchronized (lock) { + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + return; + } + try { + resp = resNetworkResult(fd); // Closes fd, marks it invalid. + } catch (ErrnoException e) { + Log.e(TAG, "resNetworkResult:" + e.toString()); + exception = e; + } + } + if (exception != null) { + answerCallback.onError(new DnsException(ERROR_SYSTEM, exception)); + return; + } + answerCallback.onAnswer(resp.answerbuf, resp.rcode); + }); + + // The file descriptor has already been unregistered, so it does not really + // matter what is returned here. In spirit 0 (meaning "unregister this FD") + // is still the closest to what the looper needs to do. When returning 0, + // Looper knows to ignore the fd if it has already been unregistered. + return 0; + }); + } + + private void cancelQuery(@NonNull FileDescriptor queryfd) { + if (!queryfd.valid()) return; + Looper.getMainLooper().getQueue().removeOnFileDescriptorEventListener(queryfd); + resNetworkCancel(queryfd); // Closes fd, marks it invalid. + } + + private void addCancellationSignal(@NonNull CancellationSignal cancellationSignal, + @NonNull FileDescriptor queryfd, @NonNull Object lock) { + cancellationSignal.setOnCancelListener(() -> { + synchronized (lock) { + cancelQuery(queryfd); + } + }); + } + + private static class DnsAddressAnswer extends DnsPacket { + private static final String TAG = "DnsResolver.DnsAddressAnswer"; + private static final boolean DBG = false; + + private final int mQueryType; + + DnsAddressAnswer(@NonNull byte[] data) throws ParseException { + super(data); + if ((mHeader.flags & (1 << 15)) == 0) { + throw new ParseException("Not an answer packet"); + } + if (mHeader.getRecordCount(QDSECTION) == 0) { + throw new ParseException("No question found"); + } + // Expect only one question in question section. + mQueryType = mRecords[QDSECTION].get(0).nsType; + } + + public @NonNull List getAddresses() { + final List results = new ArrayList(); + if (mHeader.getRecordCount(ANSECTION) == 0) return results; + + for (final DnsRecord ansSec : mRecords[ANSECTION]) { + // Only support A and AAAA, also ignore answers if query type != answer type. + int nsType = ansSec.nsType; + if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) { + continue; + } + try { + results.add(InetAddress.getByAddress(ansSec.getRR())); + } catch (UnknownHostException e) { + if (DBG) { + Log.w(TAG, "rr to address fail"); + } + } + } + return results; + } + } + +} diff --git a/framework/src/android/net/ICaptivePortal.aidl b/framework/src/android/net/ICaptivePortal.aidl new file mode 100644 index 0000000000..fe21905c70 --- /dev/null +++ b/framework/src/android/net/ICaptivePortal.aidl @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2015, 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; + +/** + * Interface to inform NetworkMonitor of decisions of app handling captive portal. + * @hide + */ +oneway interface ICaptivePortal { + void appRequest(int request); + void appResponse(int response); + void logEvent(int eventId, String packageName); +} diff --git a/framework/src/android/net/IConnectivityDiagnosticsCallback.aidl b/framework/src/android/net/IConnectivityDiagnosticsCallback.aidl new file mode 100644 index 0000000000..82b64a9280 --- /dev/null +++ b/framework/src/android/net/IConnectivityDiagnosticsCallback.aidl @@ -0,0 +1,28 @@ +/** + * + * 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; + +import android.net.ConnectivityDiagnosticsManager; +import android.net.Network; + +/** @hide */ +oneway interface IConnectivityDiagnosticsCallback { + void onConnectivityReportAvailable(in ConnectivityDiagnosticsManager.ConnectivityReport report); + void onDataStallSuspected(in ConnectivityDiagnosticsManager.DataStallReport report); + void onNetworkConnectivityReported(in Network n, boolean hasConnectivity); +} \ No newline at end of file diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl new file mode 100644 index 0000000000..1b4d2e4139 --- /dev/null +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2008, 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; + +import android.app.PendingIntent; +import android.net.ConnectionInfo; +import android.net.ConnectivityDiagnosticsManager; +import android.net.IConnectivityDiagnosticsCallback; +import android.net.IQosCallback; +import android.net.ISocketKeepaliveCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkRequest; +import android.net.NetworkState; +import android.net.ProxyInfo; +import android.net.UidRange; +import android.net.QosSocketInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.INetworkActivityListener; +import android.os.Messenger; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.ResultReceiver; + +import com.android.connectivity.aidl.INetworkAgent; +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; + +/** + * Interface that answers queries about, and allows changing, the + * state of network connectivity. + */ +/** {@hide} */ +interface IConnectivityManager +{ + Network getActiveNetwork(); + Network getActiveNetworkForUid(int uid, boolean ignoreBlocked); + @UnsupportedAppUsage + NetworkInfo getActiveNetworkInfo(); + NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked); + @UnsupportedAppUsage(maxTargetSdk = 28) + NetworkInfo getNetworkInfo(int networkType); + NetworkInfo getNetworkInfoForUid(in Network network, int uid, boolean ignoreBlocked); + @UnsupportedAppUsage + NetworkInfo[] getAllNetworkInfo(); + Network getNetworkForType(int networkType); + Network[] getAllNetworks(); + NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser( + int userId, String callingPackageName); + + boolean isNetworkSupported(int networkType); + + @UnsupportedAppUsage + LinkProperties getActiveLinkProperties(); + LinkProperties getLinkPropertiesForType(int networkType); + LinkProperties getLinkProperties(in Network network); + + NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName); + + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + NetworkState[] getAllNetworkState(); + + boolean isActiveNetworkMetered(); + + boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, + String callingPackageName, String callingAttributionTag); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getLastTetherError} as alternative") + int getLastTetherError(String iface); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetherableIfaces} as alternative") + String[] getTetherableIfaces(); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetheredIfaces} as alternative") + String[] getTetheredIfaces(); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetheringErroredIfaces} " + + "as Alternative") + String[] getTetheringErroredIfaces(); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetherableUsbRegexs} as alternative") + String[] getTetherableUsbRegexs(); + + @UnsupportedAppUsage(maxTargetSdk = 29, + publicAlternatives = "Use {@code TetheringManager#getTetherableWifiRegexs} as alternative") + String[] getTetherableWifiRegexs(); + + @UnsupportedAppUsage(maxTargetSdk = 28) + void reportInetCondition(int networkType, int percentage); + + void reportNetworkConnectivity(in Network network, boolean hasConnectivity); + + ProxyInfo getGlobalProxy(); + + void setGlobalProxy(in ProxyInfo p); + + ProxyInfo getProxyForNetwork(in Network nework); + + boolean prepareVpn(String oldPackage, String newPackage, int userId); + + void setVpnPackageAuthorization(String packageName, int userId, int vpnType); + + ParcelFileDescriptor establishVpn(in VpnConfig config); + + boolean provisionVpnProfile(in VpnProfile profile, String packageName); + + void deleteVpnProfile(String packageName); + + void startVpnProfile(String packageName); + + void stopVpnProfile(String packageName); + + VpnConfig getVpnConfig(int userId); + + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) + void startLegacyVpn(in VpnProfile profile); + + LegacyVpnInfo getLegacyVpnInfo(int userId); + + boolean updateLockdownVpn(); + boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); + boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, + in List lockdownWhitelist); + String getAlwaysOnVpnPackage(int userId); + boolean isVpnLockdownEnabled(int userId); + List getVpnLockdownWhitelist(int userId); + void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); + + void setProvisioningNotificationVisible(boolean visible, int networkType, in String action); + + void setAirplaneMode(boolean enable); + + boolean requestBandwidthUpdate(in Network network); + + int registerNetworkFactory(in Messenger messenger, in String name); + void unregisterNetworkFactory(in Messenger messenger); + + int registerNetworkProvider(in Messenger messenger, in String name); + void unregisterNetworkProvider(in Messenger messenger); + + void declareNetworkRequestUnfulfillable(in NetworkRequest request); + + Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, + in NetworkCapabilities nc, int score, in NetworkAgentConfig config, + in int factorySerialNumber); + + NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, + in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, + String callingPackageName, String callingAttributionTag); + + NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities, + in PendingIntent operation, String callingPackageName, String callingAttributionTag); + + void releasePendingNetworkRequest(in PendingIntent operation); + + NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities, + in Messenger messenger, in IBinder binder, String callingPackageName); + + void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, + in PendingIntent operation, String callingPackageName); + + void releaseNetworkRequest(in NetworkRequest networkRequest); + + void setAcceptUnvalidated(in Network network, boolean accept, boolean always); + void setAcceptPartialConnectivity(in Network network, boolean accept, boolean always); + void setAvoidUnvalidated(in Network network); + void startCaptivePortalApp(in Network network); + void startCaptivePortalAppInternal(in Network network, in Bundle appExtras); + + boolean shouldAvoidBadWifi(); + int getMultipathPreference(in Network Network); + + NetworkRequest getDefaultRequest(); + + int getRestoreDefaultNetworkDelay(int networkType); + + boolean addVpnAddress(String address, int prefixLength); + boolean removeVpnAddress(String address, int prefixLength); + boolean setUnderlyingNetworksForVpn(in Network[] networks); + + void factoryReset(); + + void startNattKeepalive(in Network network, int intervalSeconds, + in ISocketKeepaliveCallback cb, String srcAddr, int srcPort, String dstAddr); + + void startNattKeepaliveWithFd(in Network network, in ParcelFileDescriptor pfd, int resourceId, + int intervalSeconds, in ISocketKeepaliveCallback cb, String srcAddr, + String dstAddr); + + void startTcpKeepalive(in Network network, in ParcelFileDescriptor pfd, int intervalSeconds, + in ISocketKeepaliveCallback cb); + + void stopKeepalive(in Network network, int slot); + + String getCaptivePortalServerUrl(); + + byte[] getNetworkWatchlistConfigHash(); + + int getConnectionOwnerUid(in ConnectionInfo connectionInfo); + boolean isCallerCurrentAlwaysOnVpnApp(); + boolean isCallerCurrentAlwaysOnVpnLockdownApp(); + + void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, + in NetworkRequest request, String callingPackageName); + void unregisterConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback); + + IBinder startOrGetTestNetworkService(); + + void simulateDataStall(int detectionMethod, long timestampMillis, in Network network, + in PersistableBundle extras); + + void systemReady(); + + void registerNetworkActivityListener(in INetworkActivityListener l); + + void unregisterNetworkActivityListener(in INetworkActivityListener l); + + boolean isDefaultNetworkActive(); + + void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); + void unregisterQosCallback(in IQosCallback callback); +} diff --git a/framework/src/android/net/ISocketKeepaliveCallback.aidl b/framework/src/android/net/ISocketKeepaliveCallback.aidl new file mode 100644 index 0000000000..020fbcacbf --- /dev/null +++ b/framework/src/android/net/ISocketKeepaliveCallback.aidl @@ -0,0 +1,34 @@ +/** + * 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; + +/** + * Callback to provide status changes of keepalive offload. + * + * @hide + */ +oneway interface ISocketKeepaliveCallback +{ + /** The keepalive was successfully started. */ + void onStarted(int slot); + /** The keepalive was successfully stopped. */ + void onStopped(); + /** The keepalive was stopped because of an error. */ + void onError(int error); + /** The keepalive on a TCP socket was stopped because the socket received data. */ + void onDataReceived(); +} diff --git a/framework/src/android/net/ITestNetworkManager.aidl b/framework/src/android/net/ITestNetworkManager.aidl new file mode 100644 index 0000000000..2a863adde5 --- /dev/null +++ b/framework/src/android/net/ITestNetworkManager.aidl @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2018, 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; + +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.TestNetworkInterface; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + +/** + * Interface that allows for creation and management of test-only networks. + * + * @hide + */ +interface ITestNetworkManager +{ + TestNetworkInterface createTunInterface(in LinkAddress[] linkAddrs); + TestNetworkInterface createTapInterface(); + + void setupTestNetwork(in String iface, in LinkProperties lp, in boolean isMetered, + in int[] administratorUids, in IBinder binder); + + void teardownTestNetwork(int netId); +} diff --git a/framework/src/android/net/InetAddresses.java b/framework/src/android/net/InetAddresses.java new file mode 100644 index 0000000000..01b795e456 --- /dev/null +++ b/framework/src/android/net/InetAddresses.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 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; + +import android.annotation.NonNull; + +import libcore.net.InetAddressUtils; + +import java.net.InetAddress; + +/** + * Utility methods for {@link InetAddress} implementations. + */ +public class InetAddresses { + + private InetAddresses() {} + + /** + * Checks to see if the {@code address} is a numeric address (such as {@code "192.0.2.1"} or + * {@code "2001:db8::1:2"}). + * + *

A numeric address is either an IPv4 address containing exactly 4 decimal numbers or an + * IPv6 numeric address. IPv4 addresses that consist of either hexadecimal or octal digits or + * do not have exactly 4 numbers are not treated as numeric. + * + *

This method will never do a DNS lookup. + * + * @param address the address to parse. + * @return true if the supplied address is numeric, false otherwise. + */ + public static boolean isNumericAddress(@NonNull String address) { + return InetAddressUtils.isNumericAddress(address); + } + + /** + * Returns an InetAddress corresponding to the given numeric address (such + * as {@code "192.168.0.1"} or {@code "2001:4860:800d::68"}). + * + *

See {@link #isNumericAddress(String)} (String)} for a definition as to what constitutes a + * numeric address. + * + *

This method will never do a DNS lookup. + * + * @param address the address to parse, must be numeric. + * @return an {@link InetAddress} instance corresponding to the address. + * @throws IllegalArgumentException if {@code address} is not a numeric address. + */ + public static @NonNull InetAddress parseNumericAddress(@NonNull String address) { + return InetAddressUtils.parseNumericAddress(address); + } +} diff --git a/framework/src/android/net/InterfaceConfiguration.aidl b/framework/src/android/net/InterfaceConfiguration.aidl new file mode 100644 index 0000000000..8aa5e34528 --- /dev/null +++ b/framework/src/android/net/InterfaceConfiguration.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2008, 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; + +parcelable InterfaceConfiguration; diff --git a/framework/src/android/net/InvalidPacketException.java b/framework/src/android/net/InvalidPacketException.java new file mode 100644 index 0000000000..1873d778c0 --- /dev/null +++ b/framework/src/android/net/InvalidPacketException.java @@ -0,0 +1,66 @@ +/* + * 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; + +import android.annotation.IntDef; +import android.annotation.SystemApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Thrown when a packet is invalid. + * @hide + */ +@SystemApi +public final class InvalidPacketException extends Exception { + private final int mError; + + // Must match SocketKeepalive#ERROR_INVALID_IP_ADDRESS. + /** Invalid IP address. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + + // Must match SocketKeepalive#ERROR_INVALID_PORT. + /** Invalid port number. */ + public static final int ERROR_INVALID_PORT = -22; + + // Must match SocketKeepalive#ERROR_INVALID_LENGTH. + /** Invalid packet length. */ + public static final int ERROR_INVALID_LENGTH = -23; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_INVALID_IP_ADDRESS, + ERROR_INVALID_PORT, + ERROR_INVALID_LENGTH + }) + public @interface ErrorCode {} + + /** + * This packet is invalid. + * See the error code for details. + */ + public InvalidPacketException(@ErrorCode final int error) { + this.mError = error; + } + + /** Get error code. */ + public int getError() { + return mError; + } +} diff --git a/framework/src/android/net/IpConfiguration.aidl b/framework/src/android/net/IpConfiguration.aidl new file mode 100644 index 0000000000..7a30f0e79c --- /dev/null +++ b/framework/src/android/net/IpConfiguration.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 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; + +parcelable IpConfiguration; diff --git a/framework/src/android/net/IpConfiguration.java b/framework/src/android/net/IpConfiguration.java new file mode 100644 index 0000000000..0b205642b3 --- /dev/null +++ b/framework/src/android/net/IpConfiguration.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2014 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A class representing a configured network. + * @hide + */ +@SystemApi +public final class IpConfiguration implements Parcelable { + private static final String TAG = "IpConfiguration"; + + // This enum has been used by apps through reflection for many releases. + // Therefore they can't just be removed. Duplicating these constants to + // give an alternate SystemApi is a worse option than exposing them. + @SuppressLint("Enum") + public enum IpAssignment { + /* Use statically configured IP settings. Configuration can be accessed + * with staticIpConfiguration */ + STATIC, + /* Use dynamically configured IP settings */ + DHCP, + /* no IP details are assigned, this is used to indicate + * that any existing IP settings should be retained */ + UNASSIGNED + } + + /** @hide */ + public IpAssignment ipAssignment; + + /** @hide */ + public StaticIpConfiguration staticIpConfiguration; + + // This enum has been used by apps through reflection for many releases. + // Therefore they can't just be removed. Duplicating these constants to + // give an alternate SystemApi is a worse option than exposing them. + @SuppressLint("Enum") + public enum ProxySettings { + /* No proxy is to be used. Any existing proxy settings + * should be cleared. */ + NONE, + /* Use statically configured proxy. Configuration can be accessed + * with httpProxy. */ + STATIC, + /* no proxy details are assigned, this is used to indicate + * that any existing proxy settings should be retained */ + UNASSIGNED, + /* Use a Pac based proxy. + */ + PAC + } + + /** @hide */ + public ProxySettings proxySettings; + + /** @hide */ + @UnsupportedAppUsage + public ProxyInfo httpProxy; + + private void init(IpAssignment ipAssignment, + ProxySettings proxySettings, + StaticIpConfiguration staticIpConfiguration, + ProxyInfo httpProxy) { + this.ipAssignment = ipAssignment; + this.proxySettings = proxySettings; + this.staticIpConfiguration = (staticIpConfiguration == null) ? + null : new StaticIpConfiguration(staticIpConfiguration); + this.httpProxy = (httpProxy == null) ? + null : new ProxyInfo(httpProxy); + } + + public IpConfiguration() { + init(IpAssignment.UNASSIGNED, ProxySettings.UNASSIGNED, null, null); + } + + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public IpConfiguration(IpAssignment ipAssignment, + ProxySettings proxySettings, + StaticIpConfiguration staticIpConfiguration, + ProxyInfo httpProxy) { + init(ipAssignment, proxySettings, staticIpConfiguration, httpProxy); + } + + public IpConfiguration(@NonNull IpConfiguration source) { + this(); + if (source != null) { + init(source.ipAssignment, source.proxySettings, + source.staticIpConfiguration, source.httpProxy); + } + } + + public @NonNull IpAssignment getIpAssignment() { + return ipAssignment; + } + + public void setIpAssignment(@NonNull IpAssignment ipAssignment) { + this.ipAssignment = ipAssignment; + } + + public @Nullable StaticIpConfiguration getStaticIpConfiguration() { + return staticIpConfiguration; + } + + public void setStaticIpConfiguration(@Nullable StaticIpConfiguration staticIpConfiguration) { + this.staticIpConfiguration = staticIpConfiguration; + } + + public @NonNull ProxySettings getProxySettings() { + return proxySettings; + } + + public void setProxySettings(@NonNull ProxySettings proxySettings) { + this.proxySettings = proxySettings; + } + + public @Nullable ProxyInfo getHttpProxy() { + return httpProxy; + } + + public void setHttpProxy(@Nullable ProxyInfo httpProxy) { + this.httpProxy = httpProxy; + } + + @Override + public String toString() { + StringBuilder sbuf = new StringBuilder(); + sbuf.append("IP assignment: " + ipAssignment.toString()); + sbuf.append("\n"); + if (staticIpConfiguration != null) { + sbuf.append("Static configuration: " + staticIpConfiguration.toString()); + sbuf.append("\n"); + } + sbuf.append("Proxy settings: " + proxySettings.toString()); + sbuf.append("\n"); + if (httpProxy != null) { + sbuf.append("HTTP proxy: " + httpProxy.toString()); + sbuf.append("\n"); + } + + return sbuf.toString(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof IpConfiguration)) { + return false; + } + + IpConfiguration other = (IpConfiguration) o; + return this.ipAssignment == other.ipAssignment && + this.proxySettings == other.proxySettings && + Objects.equals(this.staticIpConfiguration, other.staticIpConfiguration) && + Objects.equals(this.httpProxy, other.httpProxy); + } + + @Override + public int hashCode() { + return 13 + (staticIpConfiguration != null ? staticIpConfiguration.hashCode() : 0) + + 17 * ipAssignment.ordinal() + + 47 * proxySettings.ordinal() + + 83 * httpProxy.hashCode(); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(ipAssignment.name()); + dest.writeString(proxySettings.name()); + dest.writeParcelable(staticIpConfiguration, flags); + dest.writeParcelable(httpProxy, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator() { + public IpConfiguration createFromParcel(Parcel in) { + IpConfiguration config = new IpConfiguration(); + config.ipAssignment = IpAssignment.valueOf(in.readString()); + config.proxySettings = ProxySettings.valueOf(in.readString()); + config.staticIpConfiguration = in.readParcelable(null); + config.httpProxy = in.readParcelable(null); + return config; + } + + public IpConfiguration[] newArray(int size) { + return new IpConfiguration[size]; + } + }; +} diff --git a/framework/src/android/net/IpPrefix.aidl b/framework/src/android/net/IpPrefix.aidl new file mode 100644 index 0000000000..0d70f2a1ed --- /dev/null +++ b/framework/src/android/net/IpPrefix.aidl @@ -0,0 +1,22 @@ +/** + * + * Copyright (C) 2014 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; + +// @JavaOnlyStableParcelable only affects the parcelable when built as stable aidl (aidl_interface +// build rule). IpPrefix is also used in cpp but only as non-stable aidl. +@JavaOnlyStableParcelable parcelable IpPrefix cpp_header "binder/IpPrefix.h"; diff --git a/framework/src/android/net/IpPrefix.java b/framework/src/android/net/IpPrefix.java new file mode 100644 index 0000000000..e7c801475c --- /dev/null +++ b/framework/src/android/net/IpPrefix.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2014 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; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Pair; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Comparator; + +/** + * This class represents an IP prefix, i.e., a contiguous block of IP addresses aligned on a + * power of two boundary (also known as an "IP subnet"). A prefix is specified by two pieces of + * information: + * + *

    + *
  • A starting IP address (IPv4 or IPv6). This is the first IP address of the prefix. + *
  • A prefix length. This specifies the length of the prefix by specifing the number of bits + * in the IP address, starting from the most significant bit in network byte order, that + * are constant for all addresses in the prefix. + *
+ * + * For example, the prefix 192.0.2.0/24 covers the 256 IPv4 addresses from + * 192.0.2.0 to 192.0.2.255, inclusive, and the prefix + * 2001:db8:1:2 covers the 2^64 IPv6 addresses from 2001:db8:1:2:: to + * 2001:db8:1:2:ffff:ffff:ffff:ffff, inclusive. + * + * Objects of this class are immutable. + */ +public final class IpPrefix implements Parcelable { + private final byte[] address; // network byte order + private final int prefixLength; + + private void checkAndMaskAddressAndPrefixLength() { + if (address.length != 4 && address.length != 16) { + throw new IllegalArgumentException( + "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); + } + NetworkUtils.maskRawAddress(address, prefixLength); + } + + /** + * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in + * network byte order and a prefix length. Silently truncates the address to the prefix length, + * so for example {@code 192.0.2.1/24} is silently converted to {@code 192.0.2.0/24}. + * + * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long. + * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * + * @hide + */ + public IpPrefix(@NonNull byte[] address, @IntRange(from = 0, to = 128) int prefixLength) { + this.address = address.clone(); + this.prefixLength = prefixLength; + checkAndMaskAddressAndPrefixLength(); + } + + /** + * Constructs a new {@code IpPrefix} from an IPv4 or IPv6 address and a prefix length. Silently + * truncates the address to the prefix length, so for example {@code 192.0.2.1/24} is silently + * converted to {@code 192.0.2.0/24}. + * + * @param address the IP address. Must be non-null. + * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * @hide + */ + @SystemApi + public IpPrefix(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength) { + // We don't reuse the (byte[], int) constructor because it calls clone() on the byte array, + // which is unnecessary because getAddress() already returns a clone. + this.address = address.getAddress(); + this.prefixLength = prefixLength; + checkAndMaskAddressAndPrefixLength(); + } + + /** + * Constructs a new IpPrefix from a string such as "192.0.2.1/24" or "2001:db8::1/64". + * Silently truncates the address to the prefix length, so for example {@code 192.0.2.1/24} + * is silently converted to {@code 192.0.2.0/24}. + * + * @param prefix the prefix to parse + * + * @hide + */ + @SystemApi + public IpPrefix(@NonNull String prefix) { + // We don't reuse the (InetAddress, int) constructor because "error: call to this must be + // first statement in constructor". We could factor out setting the member variables to an + // init() method, but if we did, then we'd have to make the members non-final, or "error: + // cannot assign a value to final variable address". So we just duplicate the code here. + Pair ipAndMask = NetworkUtils.parseIpAndMask(prefix); + this.address = ipAndMask.first.getAddress(); + this.prefixLength = ipAndMask.second; + checkAndMaskAddressAndPrefixLength(); + } + + /** + * Compares this {@code IpPrefix} object against the specified object in {@code obj}. Two + * objects are equal if they have the same startAddress and prefixLength. + * + * @param obj the object to be tested for equality. + * @return {@code true} if both objects are equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof IpPrefix)) { + return false; + } + IpPrefix that = (IpPrefix) obj; + return Arrays.equals(this.address, that.address) && this.prefixLength == that.prefixLength; + } + + /** + * Gets the hashcode of the represented IP prefix. + * + * @return the appropriate hashcode value. + */ + @Override + public int hashCode() { + return Arrays.hashCode(address) + 11 * prefixLength; + } + + /** + * Returns a copy of the first IP address in the prefix. Modifying the returned object does not + * change this object's contents. + * + * @return the address in the form of a byte array. + */ + public @NonNull InetAddress getAddress() { + try { + return InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte + // array is the wrong length, but we check that in the constructor. + throw new IllegalArgumentException("Address is invalid"); + } + } + + /** + * Returns a copy of the IP address bytes in network order (the highest order byte is the zeroth + * element). Modifying the returned array does not change this object's contents. + * + * @return the address in the form of a byte array. + */ + public @NonNull byte[] getRawAddress() { + return address.clone(); + } + + /** + * Returns the prefix length of this {@code IpPrefix}. + * + * @return the prefix length. + */ + @IntRange(from = 0, to = 128) + public int getPrefixLength() { + return prefixLength; + } + + /** + * Determines whether the prefix contains the specified address. + * + * @param address An {@link InetAddress} to test. + * @return {@code true} if the prefix covers the given address. {@code false} otherwise. + */ + public boolean contains(@NonNull InetAddress address) { + byte[] addrBytes = address.getAddress(); + if (addrBytes == null || addrBytes.length != this.address.length) { + return false; + } + NetworkUtils.maskRawAddress(addrBytes, prefixLength); + return Arrays.equals(this.address, addrBytes); + } + + /** + * Returns whether the specified prefix is entirely contained in this prefix. + * + * Note this is mathematical inclusion, so a prefix is always contained within itself. + * @param otherPrefix the prefix to test + * @hide + */ + public boolean containsPrefix(@NonNull IpPrefix otherPrefix) { + if (otherPrefix.getPrefixLength() < prefixLength) return false; + final byte[] otherAddress = otherPrefix.getRawAddress(); + NetworkUtils.maskRawAddress(otherAddress, prefixLength); + return Arrays.equals(otherAddress, address); + } + + /** + * @hide + */ + public boolean isIPv6() { + return getAddress() instanceof Inet6Address; + } + + /** + * @hide + */ + public boolean isIPv4() { + return getAddress() instanceof Inet4Address; + } + + /** + * Returns a string representation of this {@code IpPrefix}. + * + * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::/64"}. + */ + public String toString() { + try { + return InetAddress.getByAddress(address).getHostAddress() + "/" + prefixLength; + } catch(UnknownHostException e) { + // Cosmic rays? + throw new IllegalStateException("IpPrefix with invalid address! Shouldn't happen.", e); + } + } + + /** + * Implement the Parcelable interface. + */ + public int describeContents() { + return 0; + } + + /** + * Implement the Parcelable interface. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(address); + dest.writeInt(prefixLength); + } + + /** + * Returns a comparator ordering IpPrefixes by length, shorter to longer. + * Contents of the address will break ties. + * @hide + */ + public static Comparator lengthComparator() { + return new Comparator() { + @Override + public int compare(IpPrefix prefix1, IpPrefix prefix2) { + if (prefix1.isIPv4()) { + if (prefix2.isIPv6()) return -1; + } else { + if (prefix2.isIPv4()) return 1; + } + final int p1len = prefix1.getPrefixLength(); + final int p2len = prefix2.getPrefixLength(); + if (p1len < p2len) return -1; + if (p2len < p1len) return 1; + final byte[] a1 = prefix1.address; + final byte[] a2 = prefix2.address; + final int len = a1.length < a2.length ? a1.length : a2.length; + for (int i = 0; i < len; ++i) { + if (a1[i] < a2[i]) return -1; + if (a1[i] > a2[i]) return 1; + } + if (a2.length < len) return 1; + if (a1.length < len) return -1; + return 0; + } + }; + } + + /** + * Implement the Parcelable interface. + */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public IpPrefix createFromParcel(Parcel in) { + byte[] address = in.createByteArray(); + int prefixLength = in.readInt(); + return new IpPrefix(address, prefixLength); + } + + public IpPrefix[] newArray(int size) { + return new IpPrefix[size]; + } + }; +} diff --git a/framework/src/android/net/KeepalivePacketData.aidl b/framework/src/android/net/KeepalivePacketData.aidl new file mode 100644 index 0000000000..d456b53fd1 --- /dev/null +++ b/framework/src/android/net/KeepalivePacketData.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 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; + +parcelable KeepalivePacketData; diff --git a/framework/src/android/net/KeepalivePacketData.java b/framework/src/android/net/KeepalivePacketData.java new file mode 100644 index 0000000000..5877f1f4e2 --- /dev/null +++ b/framework/src/android/net/KeepalivePacketData.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015 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; + +import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS; +import static android.net.InvalidPacketException.ERROR_INVALID_PORT; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.util.Log; + +import com.android.net.module.util.IpUtils; + +import java.net.InetAddress; + +/** + * Represents the actual packets that are sent by the + * {@link android.net.SocketKeepalive} API. + * @hide + */ +@SystemApi +public class KeepalivePacketData { + private static final String TAG = "KeepalivePacketData"; + + /** Source IP address */ + @NonNull + private final InetAddress mSrcAddress; + + /** Destination IP address */ + @NonNull + private final InetAddress mDstAddress; + + /** Source port */ + private final int mSrcPort; + + /** Destination port */ + private final int mDstPort; + + /** Packet data. A raw byte string of packet data, not including the link-layer header. */ + private final byte[] mPacket; + + // Note: If you add new fields, please modify the parcelling code in the child classes. + + + // This should only be constructed via static factory methods, such as + // nattKeepalivePacket. + /** + * A holding class for data necessary to build a keepalive packet. + */ + protected KeepalivePacketData(@NonNull InetAddress srcAddress, + @IntRange(from = 0, to = 65535) int srcPort, @NonNull InetAddress dstAddress, + @IntRange(from = 0, to = 65535) int dstPort, + @NonNull byte[] data) throws InvalidPacketException { + this.mSrcAddress = srcAddress; + this.mDstAddress = dstAddress; + this.mSrcPort = srcPort; + this.mDstPort = dstPort; + this.mPacket = data; + + // Check we have two IP addresses of the same family. + if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName() + .equals(dstAddress.getClass().getName())) { + Log.e(TAG, "Invalid or mismatched InetAddresses in KeepalivePacketData"); + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + // Check the ports. + if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) { + Log.e(TAG, "Invalid ports in KeepalivePacketData"); + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + } + + /** Get source IP address. */ + @NonNull + public InetAddress getSrcAddress() { + return mSrcAddress; + } + + /** Get destination IP address. */ + @NonNull + public InetAddress getDstAddress() { + return mDstAddress; + } + + /** Get source port number. */ + public int getSrcPort() { + return mSrcPort; + } + + /** Get destination port number. */ + public int getDstPort() { + return mDstPort; + } + + /** + * Returns a byte array of the given packet data. + */ + @NonNull + public byte[] getPacket() { + return mPacket.clone(); + } + +} diff --git a/framework/src/android/net/LinkAddress.aidl b/framework/src/android/net/LinkAddress.aidl new file mode 100644 index 0000000000..9c804db08d --- /dev/null +++ b/framework/src/android/net/LinkAddress.aidl @@ -0,0 +1,21 @@ +/** + * + * Copyright (C) 2010 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; + +@JavaOnlyStableParcelable parcelable LinkAddress; + diff --git a/framework/src/android/net/LinkAddress.java b/framework/src/android/net/LinkAddress.java new file mode 100644 index 0000000000..44d25a1ab0 --- /dev/null +++ b/framework/src/android/net/LinkAddress.java @@ -0,0 +1,549 @@ +/* + * Copyright (C) 2010 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; + +import static android.system.OsConstants.IFA_F_DADFAILED; +import static android.system.OsConstants.IFA_F_DEPRECATED; +import static android.system.OsConstants.IFA_F_OPTIMISTIC; +import static android.system.OsConstants.IFA_F_PERMANENT; +import static android.system.OsConstants.IFA_F_TENTATIVE; +import static android.system.OsConstants.RT_SCOPE_HOST; +import static android.system.OsConstants.RT_SCOPE_LINK; +import static android.system.OsConstants.RT_SCOPE_SITE; +import static android.system.OsConstants.RT_SCOPE_UNIVERSE; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.Pair; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.UnknownHostException; +import java.util.Objects; + +/** + * Identifies an IP address on a network link. + * + * A {@code LinkAddress} consists of: + *
    + *
  • An IP address and prefix length (e.g., {@code 2001:db8::1/64} or {@code 192.0.2.1/24}). + * The address must be unicast, as multicast addresses cannot be assigned to interfaces. + *
  • Address flags: A bitmask of {@code OsConstants.IFA_F_*} values representing properties + * of the address (e.g., {@code android.system.OsConstants.IFA_F_OPTIMISTIC}). + *
  • Address scope: One of the {@code OsConstants.IFA_F_*} values; defines the scope in which + * the address is unique (e.g., + * {@code android.system.OsConstants.RT_SCOPE_LINK} or + * {@code android.system.OsConstants.RT_SCOPE_UNIVERSE}). + *
+ */ +public class LinkAddress implements Parcelable { + + /** + * Indicates the deprecation or expiration time is unknown + * @hide + */ + @SystemApi + public static final long LIFETIME_UNKNOWN = -1; + + /** + * Indicates this address is permanent. + * @hide + */ + @SystemApi + public static final long LIFETIME_PERMANENT = Long.MAX_VALUE; + + /** + * IPv4 or IPv6 address. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private InetAddress address; + + /** + * Prefix length. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private int prefixLength; + + /** + * Address flags. A bitmask of {@code IFA_F_*} values. Note that {@link #getFlags()} may not + * return these exact values. For example, it may set or clear the {@code IFA_F_DEPRECATED} + * flag depending on the current preferred lifetime. + */ + private int flags; + + /** + * Address scope. One of the RT_SCOPE_* constants. + */ + private int scope; + + /** + * The time, as reported by {@link SystemClock#elapsedRealtime}, when this LinkAddress will be + * or was deprecated. At the time existing connections can still use this address until it + * expires, but new connections should use the new address. {@link #LIFETIME_UNKNOWN} indicates + * this information is not available. {@link #LIFETIME_PERMANENT} indicates this + * {@link LinkAddress} will never be deprecated. + */ + private long deprecationTime; + + /** + * The time, as reported by {@link SystemClock#elapsedRealtime}, when this {@link LinkAddress} + * will expire and be removed from the interface. {@link #LIFETIME_UNKNOWN} indicates this + * information is not available. {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} + * will never expire. + */ + private long expirationTime; + + /** + * Utility function to determines the scope of a unicast address. Per RFC 4291 section 2.5 and + * RFC 6724 section 3.2. + * @hide + */ + private static int scopeForUnicastAddress(InetAddress addr) { + if (addr.isAnyLocalAddress()) { + return RT_SCOPE_HOST; + } + + if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) { + return RT_SCOPE_LINK; + } + + // isSiteLocalAddress() returns true for private IPv4 addresses, but RFC 6724 section 3.2 + // says that they are assigned global scope. + if (!(addr instanceof Inet4Address) && addr.isSiteLocalAddress()) { + return RT_SCOPE_SITE; + } + + return RT_SCOPE_UNIVERSE; + } + + /** + * Utility function to check if |address| is a Unique Local IPv6 Unicast Address + * (a.k.a. "ULA"; RFC 4193). + * + * Per RFC 4193 section 8, fc00::/7 identifies these addresses. + */ + private boolean isIpv6ULA() { + if (isIpv6()) { + byte[] bytes = address.getAddress(); + return ((bytes[0] & (byte)0xfe) == (byte)0xfc); + } + return false; + } + + /** + * @return true if the address is IPv6. + * @hide + */ + @SystemApi + public boolean isIpv6() { + return address instanceof Inet6Address; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return true if the address is IPv6. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean isIPv6() { + return isIpv6(); + } + + /** + * @return true if the address is IPv4 or is a mapped IPv4 address. + * @hide + */ + @SystemApi + public boolean isIpv4() { + return address instanceof Inet4Address; + } + + /** + * Utility function for the constructors. + */ + private void init(InetAddress address, int prefixLength, int flags, int scope, + long deprecationTime, long expirationTime) { + if (address == null || + address.isMulticastAddress() || + prefixLength < 0 || + (address instanceof Inet4Address && prefixLength > 32) || + (prefixLength > 128)) { + throw new IllegalArgumentException("Bad LinkAddress params " + address + + "/" + prefixLength); + } + + // deprecation time and expiration time must be both provided, or neither. + if ((deprecationTime == LIFETIME_UNKNOWN) != (expirationTime == LIFETIME_UNKNOWN)) { + throw new IllegalArgumentException( + "Must not specify only one of deprecation time and expiration time"); + } + + // deprecation time needs to be a positive value. + if (deprecationTime != LIFETIME_UNKNOWN && deprecationTime < 0) { + throw new IllegalArgumentException("invalid deprecation time " + deprecationTime); + } + + // expiration time needs to be a positive value. + if (expirationTime != LIFETIME_UNKNOWN && expirationTime < 0) { + throw new IllegalArgumentException("invalid expiration time " + expirationTime); + } + + // expiration time can't be earlier than deprecation time + if (deprecationTime != LIFETIME_UNKNOWN && expirationTime != LIFETIME_UNKNOWN + && expirationTime < deprecationTime) { + throw new IllegalArgumentException("expiration earlier than deprecation (" + + deprecationTime + ", " + expirationTime + ")"); + } + + this.address = address; + this.prefixLength = prefixLength; + this.flags = flags; + this.scope = scope; + this.deprecationTime = deprecationTime; + this.expirationTime = expirationTime; + } + + /** + * Constructs a new {@code LinkAddress} from an {@code InetAddress} and prefix length, with + * the specified flags and scope. Flags and scope are not checked for validity. + * + * @param address The IP address. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address. + * @param scope An integer defining the scope in which the address is unique (e.g., + * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}). + * @hide + */ + @SystemApi + public LinkAddress(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength, + int flags, int scope) { + init(address, prefixLength, flags, scope, LIFETIME_UNKNOWN, LIFETIME_UNKNOWN); + } + + /** + * Constructs a new {@code LinkAddress} from an {@code InetAddress}, prefix length, with + * the specified flags, scope, deprecation time, and expiration time. Flags and scope are not + * checked for validity. The value of the {@code IFA_F_DEPRECATED} and {@code IFA_F_PERMANENT} + * flag will be adjusted based on the passed-in lifetimes. + * + * @param address The IP address. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * @param flags A bitmask of {@code IFA_F_*} values representing properties of the address. + * @param scope An integer defining the scope in which the address is unique (e.g., + * {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}). + * @param deprecationTime The time, as reported by {@link SystemClock#elapsedRealtime}, when + * this {@link LinkAddress} will be or was deprecated. At the time + * existing connections can still use this address until it expires, but + * new connections should use the new address. {@link #LIFETIME_UNKNOWN} + * indicates this information is not available. + * {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} will + * never be deprecated. + * @param expirationTime The time, as reported by {@link SystemClock#elapsedRealtime}, when this + * {@link LinkAddress} will expire and be removed from the interface. + * {@link #LIFETIME_UNKNOWN} indicates this information is not available. + * {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} will + * never expire. + * @hide + */ + @SystemApi + public LinkAddress(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength, + int flags, int scope, long deprecationTime, long expirationTime) { + init(address, prefixLength, flags, scope, deprecationTime, expirationTime); + } + + /** + * Constructs a new {@code LinkAddress} from an {@code InetAddress} and a prefix length. + * The flags are set to zero and the scope is determined from the address. + * @param address The IP address. + * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * @hide + */ + @SystemApi + public LinkAddress(@NonNull InetAddress address, + @IntRange(from = 0, to = 128) int prefixLength) { + this(address, prefixLength, 0, 0); + this.scope = scopeForUnicastAddress(address); + } + + /** + * Constructs a new {@code LinkAddress} from an {@code InterfaceAddress}. + * The flags are set to zero and the scope is determined from the address. + * @param interfaceAddress The interface address. + * @hide + */ + public LinkAddress(@NonNull InterfaceAddress interfaceAddress) { + this(interfaceAddress.getAddress(), + interfaceAddress.getNetworkPrefixLength()); + } + + /** + * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or + * "2001:db8::1/64". The flags are set to zero and the scope is determined from the address. + * @param address The string to parse. + * @hide + */ + @SystemApi + public LinkAddress(@NonNull String address) { + this(address, 0, 0); + this.scope = scopeForUnicastAddress(this.address); + } + + /** + * Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or + * "2001:db8::1/64", with the specified flags and scope. + * @param address The string to parse. + * @param flags The address flags. + * @param scope The address scope. + * @hide + */ + @SystemApi + public LinkAddress(@NonNull String address, int flags, int scope) { + // This may throw an IllegalArgumentException; catching it is the caller's responsibility. + // TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24". + Pair ipAndMask = NetworkUtils.parseIpAndMask(address); + init(ipAndMask.first, ipAndMask.second, flags, scope, LIFETIME_UNKNOWN, LIFETIME_UNKNOWN); + } + + /** + * Returns a string representation of this address, such as "192.0.2.1/24" or "2001:db8::1/64". + * The string representation does not contain the flags and scope, just the address and prefix + * length. + */ + @Override + public String toString() { + return address.getHostAddress() + "/" + prefixLength; + } + + /** + * Compares this {@code LinkAddress} instance against {@code obj}. Two addresses are equal if + * their address, prefix length, flags and scope are equal. Thus, for example, two addresses + * that have the same address and prefix length are not equal if one of them is deprecated and + * the other is not. + * + * @param obj the object to be tested for equality. + * @return {@code true} if both objects are equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof LinkAddress)) { + return false; + } + LinkAddress linkAddress = (LinkAddress) obj; + return this.address.equals(linkAddress.address) + && this.prefixLength == linkAddress.prefixLength + && this.flags == linkAddress.flags + && this.scope == linkAddress.scope + && this.deprecationTime == linkAddress.deprecationTime + && this.expirationTime == linkAddress.expirationTime; + } + + /** + * Returns a hashcode for this address. + */ + @Override + public int hashCode() { + return Objects.hash(address, prefixLength, flags, scope, deprecationTime, expirationTime); + } + + /** + * Determines whether this {@code LinkAddress} and the provided {@code LinkAddress} + * represent the same address. Two {@code LinkAddresses} represent the same address + * if they have the same IP address and prefix length, even if their properties are + * different. + * + * @param other the {@code LinkAddress} to compare to. + * @return {@code true} if both objects have the same address and prefix length, {@code false} + * otherwise. + * @hide + */ + @SystemApi + public boolean isSameAddressAs(@Nullable LinkAddress other) { + if (other == null) { + return false; + } + return address.equals(other.address) && prefixLength == other.prefixLength; + } + + /** + * Returns the {@link InetAddress} of this {@code LinkAddress}. + */ + public InetAddress getAddress() { + return address; + } + + /** + * Returns the prefix length of this {@code LinkAddress}. + */ + @IntRange(from = 0, to = 128) + public int getPrefixLength() { + return prefixLength; + } + + /** + * Returns the prefix length of this {@code LinkAddress}. + * TODO: Delete all callers and remove in favour of getPrefixLength(). + * @hide + */ + @UnsupportedAppUsage + @IntRange(from = 0, to = 128) + public int getNetworkPrefixLength() { + return getPrefixLength(); + } + + /** + * Returns the flags of this {@code LinkAddress}. + */ + public int getFlags() { + int flags = this.flags; + if (deprecationTime != LIFETIME_UNKNOWN) { + if (SystemClock.elapsedRealtime() >= deprecationTime) { + flags |= IFA_F_DEPRECATED; + } else { + // If deprecation time is in the future, or permanent. + flags &= ~IFA_F_DEPRECATED; + } + } + + if (expirationTime == LIFETIME_PERMANENT) { + flags |= IFA_F_PERMANENT; + } else if (expirationTime != LIFETIME_UNKNOWN) { + // If we know this address expired or will expire in the future, then this address + // should not be permanent. + flags &= ~IFA_F_PERMANENT; + } + + // Do no touch the original flags. Return the adjusted flags here. + return flags; + } + + /** + * Returns the scope of this {@code LinkAddress}. + */ + public int getScope() { + return scope; + } + + /** + * Get the deprecation time, as reported by {@link SystemClock#elapsedRealtime}, when this + * {@link LinkAddress} will be or was deprecated. At the time existing connections can still use + * this address until it expires, but new connections should use the new address. + * + * @return The deprecation time in milliseconds. {@link #LIFETIME_UNKNOWN} indicates this + * information is not available. {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} + * will never be deprecated. + * + * @hide + */ + @SystemApi + public long getDeprecationTime() { + return deprecationTime; + } + + /** + * Get the expiration time, as reported by {@link SystemClock#elapsedRealtime}, when this + * {@link LinkAddress} will expire and be removed from the interface. + * + * @return The expiration time in milliseconds. {@link #LIFETIME_UNKNOWN} indicates this + * information is not available. {@link #LIFETIME_PERMANENT} indicates this {@link LinkAddress} + * will never expire. + * + * @hide + */ + @SystemApi + public long getExpirationTime() { + return expirationTime; + } + + /** + * Returns true if this {@code LinkAddress} is global scope and preferred (i.e., not currently + * deprecated). + * + * @hide + */ + @SystemApi + public boolean isGlobalPreferred() { + /** + * Note that addresses flagged as IFA_F_OPTIMISTIC are + * simultaneously flagged as IFA_F_TENTATIVE (when the tentative + * state has cleared either DAD has succeeded or failed, and both + * flags are cleared regardless). + */ + int flags = getFlags(); + return (scope == RT_SCOPE_UNIVERSE + && !isIpv6ULA() + && (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED)) == 0L + && ((flags & IFA_F_TENTATIVE) == 0L || (flags & IFA_F_OPTIMISTIC) != 0L)); + } + + /** + * Implement the Parcelable interface. + */ + public int describeContents() { + return 0; + } + + /** + * Implement the Parcelable interface. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(address.getAddress()); + dest.writeInt(prefixLength); + dest.writeInt(this.flags); + dest.writeInt(scope); + dest.writeLong(deprecationTime); + dest.writeLong(expirationTime); + } + + /** + * Implement the Parcelable interface. + */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public LinkAddress createFromParcel(Parcel in) { + InetAddress address = null; + try { + address = InetAddress.getByAddress(in.createByteArray()); + } catch (UnknownHostException e) { + // Nothing we can do here. When we call the constructor, we'll throw an + // IllegalArgumentException, because a LinkAddress can't have a null + // InetAddress. + } + int prefixLength = in.readInt(); + int flags = in.readInt(); + int scope = in.readInt(); + long deprecationTime = in.readLong(); + long expirationTime = in.readLong(); + return new LinkAddress(address, prefixLength, flags, scope, deprecationTime, + expirationTime); + } + + public LinkAddress[] newArray(int size) { + return new LinkAddress[size]; + } + }; +} diff --git a/framework/src/android/net/LinkProperties.aidl b/framework/src/android/net/LinkProperties.aidl new file mode 100644 index 0000000000..a8b3c7b039 --- /dev/null +++ b/framework/src/android/net/LinkProperties.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright (C) 2010 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; + +@JavaOnlyStableParcelable parcelable LinkProperties; diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java new file mode 100644 index 0000000000..486e2d74dd --- /dev/null +++ b/framework/src/android/net/LinkProperties.java @@ -0,0 +1,1823 @@ +/* + * Copyright (C) 2010 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.net.module.util.LinkPropertiesUtils; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; + +/** + * Describes the properties of a network link. + * + * A link represents a connection to a network. + * It may have multiple addresses and multiple gateways, + * multiple dns servers but only one http proxy and one + * network interface. + * + * Note that this is just a holder of data. Modifying it + * does not affect live networks. + * + */ +public final class LinkProperties implements Parcelable { + // The interface described by the network link. + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private String mIfaceName; + private final ArrayList mLinkAddresses = new ArrayList<>(); + private final ArrayList mDnses = new ArrayList<>(); + // PCSCF addresses are addresses of SIP proxies that only exist for the IMS core service. + private final ArrayList mPcscfs = new ArrayList(); + private final ArrayList mValidatedPrivateDnses = new ArrayList<>(); + private boolean mUsePrivateDns; + private String mPrivateDnsServerName; + private String mDomains; + private ArrayList mRoutes = new ArrayList<>(); + private Inet4Address mDhcpServerAddress; + private ProxyInfo mHttpProxy; + private int mMtu; + // in the format "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max" + private String mTcpBufferSizes; + private IpPrefix mNat64Prefix; + private boolean mWakeOnLanSupported; + private Uri mCaptivePortalApiUrl; + private CaptivePortalData mCaptivePortalData; + + /** + * Indicates whether parceling should preserve fields that are set based on permissions of + * the process receiving the {@link LinkProperties}. + */ + private final transient boolean mParcelSensitiveFields; + + private static final int MIN_MTU = 68; + + private static final int MIN_MTU_V6 = 1280; + + private static final int MAX_MTU = 10000; + + private static final int INET6_ADDR_LENGTH = 16; + + // Stores the properties of links that are "stacked" above this link. + // Indexed by interface name to allow modification and to prevent duplicates being added. + private Hashtable mStackedLinks = new Hashtable<>(); + + /** + * @hide + */ + @UnsupportedAppUsage(implicitMember = + "values()[Landroid/net/LinkProperties$ProvisioningChange;") + public enum ProvisioningChange { + @UnsupportedAppUsage + STILL_NOT_PROVISIONED, + @UnsupportedAppUsage + LOST_PROVISIONING, + @UnsupportedAppUsage + GAINED_PROVISIONING, + @UnsupportedAppUsage + STILL_PROVISIONED, + } + + /** + * Compare the provisioning states of two LinkProperties instances. + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static ProvisioningChange compareProvisioning( + LinkProperties before, LinkProperties after) { + if (before.isProvisioned() && after.isProvisioned()) { + // On dual-stack networks, DHCPv4 renewals can occasionally fail. + // When this happens, IPv6-reachable services continue to function + // normally but IPv4-only services (naturally) fail. + // + // When an application using an IPv4-only service reports a bad + // network condition to the framework, attempts to re-validate + // the network succeed (since we support IPv6-only networks) and + // nothing is changed. + // + // For users, this is confusing and unexpected behaviour, and is + // not necessarily easy to diagnose. Therefore, we treat changing + // from a dual-stack network to an IPv6-only network equivalent to + // a total loss of provisioning. + // + // For one such example of this, see b/18867306. + // + // Additionally, losing IPv6 provisioning can result in TCP + // connections getting stuck until timeouts fire and other + // baffling failures. Therefore, loss of either IPv4 or IPv6 on a + // previously dual-stack network is deemed a lost of provisioning. + if ((before.isIpv4Provisioned() && !after.isIpv4Provisioned()) + || (before.isIpv6Provisioned() && !after.isIpv6Provisioned())) { + return ProvisioningChange.LOST_PROVISIONING; + } + return ProvisioningChange.STILL_PROVISIONED; + } else if (before.isProvisioned() && !after.isProvisioned()) { + return ProvisioningChange.LOST_PROVISIONING; + } else if (!before.isProvisioned() && after.isProvisioned()) { + return ProvisioningChange.GAINED_PROVISIONING; + } else { // !before.isProvisioned() && !after.isProvisioned() + return ProvisioningChange.STILL_NOT_PROVISIONED; + } + } + + /** + * Constructs a new {@code LinkProperties} with default values. + */ + public LinkProperties() { + mParcelSensitiveFields = false; + } + + /** + * @hide + */ + @SystemApi + public LinkProperties(@Nullable LinkProperties source) { + this(source, false /* parcelSensitiveFields */); + } + + /** + * Create a copy of a {@link LinkProperties} that may preserve fields that were set + * based on the permissions of the process that originally received it. + * + *

By default {@link LinkProperties} does not preserve such fields during parceling, as + * they should not be shared outside of the process that receives them without appropriate + * checks. + * @param parcelSensitiveFields Whether the sensitive fields should be kept when parceling + * @hide + */ + @SystemApi + public LinkProperties(@Nullable LinkProperties source, boolean parcelSensitiveFields) { + mParcelSensitiveFields = parcelSensitiveFields; + if (source == null) return; + mIfaceName = source.mIfaceName; + mLinkAddresses.addAll(source.mLinkAddresses); + mDnses.addAll(source.mDnses); + mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses); + mUsePrivateDns = source.mUsePrivateDns; + mPrivateDnsServerName = source.mPrivateDnsServerName; + mPcscfs.addAll(source.mPcscfs); + mDomains = source.mDomains; + mRoutes.addAll(source.mRoutes); + mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy); + for (LinkProperties l: source.mStackedLinks.values()) { + addStackedLink(l); + } + setMtu(source.mMtu); + setDhcpServerAddress(source.getDhcpServerAddress()); + mTcpBufferSizes = source.mTcpBufferSizes; + mNat64Prefix = source.mNat64Prefix; + mWakeOnLanSupported = source.mWakeOnLanSupported; + mCaptivePortalApiUrl = source.mCaptivePortalApiUrl; + mCaptivePortalData = source.mCaptivePortalData; + } + + /** + * Sets the interface name for this link. All {@link RouteInfo} already set for this + * will have their interface changed to match this new value. + * + * @param iface The name of the network interface used for this link. + */ + public void setInterfaceName(@Nullable String iface) { + mIfaceName = iface; + ArrayList newRoutes = new ArrayList<>(mRoutes.size()); + for (RouteInfo route : mRoutes) { + newRoutes.add(routeWithInterface(route)); + } + mRoutes = newRoutes; + } + + /** + * Gets the interface name for this link. May be {@code null} if not set. + * + * @return The interface name set for this link or {@code null}. + */ + public @Nullable String getInterfaceName() { + return mIfaceName; + } + + /** + * @hide + */ + @SystemApi + public @NonNull List getAllInterfaceNames() { + List interfaceNames = new ArrayList<>(mStackedLinks.size() + 1); + if (mIfaceName != null) interfaceNames.add(mIfaceName); + for (LinkProperties stacked: mStackedLinks.values()) { + interfaceNames.addAll(stacked.getAllInterfaceNames()); + } + return interfaceNames; + } + + /** + * Returns all the addresses on this link. We often think of a link having a single address, + * however, particularly with Ipv6 several addresses are typical. Note that the + * {@code LinkProperties} actually contains {@link LinkAddress} objects which also include + * prefix lengths for each address. This is a simplified utility alternative to + * {@link LinkProperties#getLinkAddresses}. + * + * @return An unmodifiable {@link List} of {@link InetAddress} for this link. + * @hide + */ + @SystemApi + public @NonNull List getAddresses() { + final List addresses = new ArrayList<>(); + for (LinkAddress linkAddress : mLinkAddresses) { + addresses.add(linkAddress.getAddress()); + } + return Collections.unmodifiableList(addresses); + } + + /** + * Returns all the addresses on this link and all the links stacked above it. + * @hide + */ + @UnsupportedAppUsage + public @NonNull List getAllAddresses() { + List addresses = new ArrayList<>(); + for (LinkAddress linkAddress : mLinkAddresses) { + addresses.add(linkAddress.getAddress()); + } + for (LinkProperties stacked: mStackedLinks.values()) { + addresses.addAll(stacked.getAllAddresses()); + } + return addresses; + } + + private int findLinkAddressIndex(LinkAddress address) { + for (int i = 0; i < mLinkAddresses.size(); i++) { + if (mLinkAddresses.get(i).isSameAddressAs(address)) { + return i; + } + } + return -1; + } + + /** + * Adds a {@link LinkAddress} to this {@code LinkProperties} if a {@link LinkAddress} of the + * same address/prefix does not already exist. If it does exist it is replaced. + * @param address The {@code LinkAddress} to add. + * @return true if {@code address} was added or updated, false otherwise. + * @hide + */ + @SystemApi + public boolean addLinkAddress(@NonNull LinkAddress address) { + if (address == null) { + return false; + } + int i = findLinkAddressIndex(address); + if (i < 0) { + // Address was not present. Add it. + mLinkAddresses.add(address); + return true; + } else if (mLinkAddresses.get(i).equals(address)) { + // Address was present and has same properties. Do nothing. + return false; + } else { + // Address was present and has different properties. Update it. + mLinkAddresses.set(i, address); + return true; + } + } + + /** + * Removes a {@link LinkAddress} from this {@code LinkProperties}. Specifically, matches + * and {@link LinkAddress} with the same address and prefix. + * + * @param toRemove A {@link LinkAddress} specifying the address to remove. + * @return true if the address was removed, false if it did not exist. + * @hide + */ + @SystemApi + public boolean removeLinkAddress(@NonNull LinkAddress toRemove) { + int i = findLinkAddressIndex(toRemove); + if (i >= 0) { + mLinkAddresses.remove(i); + return true; + } + return false; + } + + /** + * Returns all the {@link LinkAddress} on this link. Typically a link will have + * one IPv4 address and one or more IPv6 addresses. + * + * @return An unmodifiable {@link List} of {@link LinkAddress} for this link. + */ + public @NonNull List getLinkAddresses() { + return Collections.unmodifiableList(mLinkAddresses); + } + + /** + * Returns all the addresses on this link and all the links stacked above it. + * @hide + */ + @SystemApi + public @NonNull List getAllLinkAddresses() { + List addresses = new ArrayList<>(mLinkAddresses); + for (LinkProperties stacked: mStackedLinks.values()) { + addresses.addAll(stacked.getAllLinkAddresses()); + } + return addresses; + } + + /** + * Replaces the {@link LinkAddress} in this {@code LinkProperties} with + * the given {@link Collection} of {@link LinkAddress}. + * + * @param addresses The {@link Collection} of {@link LinkAddress} to set in this + * object. + */ + public void setLinkAddresses(@NonNull Collection addresses) { + mLinkAddresses.clear(); + for (LinkAddress address: addresses) { + addLinkAddress(address); + } + } + + /** + * Adds the given {@link InetAddress} to the list of DNS servers, if not present. + * + * @param dnsServer The {@link InetAddress} to add to the list of DNS servers. + * @return true if the DNS server was added, false if it was already present. + * @hide + */ + @SystemApi + public boolean addDnsServer(@NonNull InetAddress dnsServer) { + if (dnsServer != null && !mDnses.contains(dnsServer)) { + mDnses.add(dnsServer); + return true; + } + return false; + } + + /** + * Removes the given {@link InetAddress} from the list of DNS servers. + * + * @param dnsServer The {@link InetAddress} to remove from the list of DNS servers. + * @return true if the DNS server was removed, false if it did not exist. + * @hide + */ + @SystemApi + public boolean removeDnsServer(@NonNull InetAddress dnsServer) { + return mDnses.remove(dnsServer); + } + + /** + * Replaces the DNS servers in this {@code LinkProperties} with + * the given {@link Collection} of {@link InetAddress} objects. + * + * @param dnsServers The {@link Collection} of DNS servers to set in this object. + */ + public void setDnsServers(@NonNull Collection dnsServers) { + mDnses.clear(); + for (InetAddress dnsServer: dnsServers) { + addDnsServer(dnsServer); + } + } + + /** + * Returns all the {@link InetAddress} for DNS servers on this link. + * + * @return An unmodifiable {@link List} of {@link InetAddress} for DNS servers on + * this link. + */ + public @NonNull List getDnsServers() { + return Collections.unmodifiableList(mDnses); + } + + /** + * Set whether private DNS is currently in use on this network. + * + * @param usePrivateDns The private DNS state. + * @hide + */ + @SystemApi + public void setUsePrivateDns(boolean usePrivateDns) { + mUsePrivateDns = usePrivateDns; + } + + /** + * Returns whether private DNS is currently in use on this network. When + * private DNS is in use, applications must not send unencrypted DNS + * queries as doing so could reveal private user information. Furthermore, + * if private DNS is in use and {@link #getPrivateDnsServerName} is not + * {@code null}, DNS queries must be sent to the specified DNS server. + * + * @return {@code true} if private DNS is in use, {@code false} otherwise. + */ + public boolean isPrivateDnsActive() { + return mUsePrivateDns; + } + + /** + * Set the name of the private DNS server to which private DNS queries + * should be sent when in strict mode. This value should be {@code null} + * when private DNS is off or in opportunistic mode. + * + * @param privateDnsServerName The private DNS server name. + * @hide + */ + @SystemApi + public void setPrivateDnsServerName(@Nullable String privateDnsServerName) { + mPrivateDnsServerName = privateDnsServerName; + } + + /** + * Set DHCP server address. + * + * @param serverAddress the server address to set. + */ + public void setDhcpServerAddress(@Nullable Inet4Address serverAddress) { + mDhcpServerAddress = serverAddress; + } + + /** + * Get DHCP server address + * + * @return The current DHCP server address. + */ + public @Nullable Inet4Address getDhcpServerAddress() { + return mDhcpServerAddress; + } + + /** + * Returns the private DNS server name that is in use. If not {@code null}, + * private DNS is in strict mode. In this mode, applications should ensure + * that all DNS queries are encrypted and sent to this hostname and that + * queries are only sent if the hostname's certificate is valid. If + * {@code null} and {@link #isPrivateDnsActive} is {@code true}, private + * DNS is in opportunistic mode, and applications should ensure that DNS + * queries are encrypted and sent to a DNS server returned by + * {@link #getDnsServers}. System DNS will handle each of these cases + * correctly, but applications implementing their own DNS lookups must make + * sure to follow these requirements. + * + * @return The private DNS server name. + */ + public @Nullable String getPrivateDnsServerName() { + return mPrivateDnsServerName; + } + + /** + * Adds the given {@link InetAddress} to the list of validated private DNS servers, + * if not present. This is distinct from the server name in that these are actually + * resolved addresses. + * + * @param dnsServer The {@link InetAddress} to add to the list of validated private DNS servers. + * @return true if the DNS server was added, false if it was already present. + * @hide + */ + public boolean addValidatedPrivateDnsServer(@NonNull InetAddress dnsServer) { + if (dnsServer != null && !mValidatedPrivateDnses.contains(dnsServer)) { + mValidatedPrivateDnses.add(dnsServer); + return true; + } + return false; + } + + /** + * Removes the given {@link InetAddress} from the list of validated private DNS servers. + * + * @param dnsServer The {@link InetAddress} to remove from the list of validated private DNS + * servers. + * @return true if the DNS server was removed, false if it did not exist. + * @hide + */ + public boolean removeValidatedPrivateDnsServer(@NonNull InetAddress dnsServer) { + return mValidatedPrivateDnses.remove(dnsServer); + } + + /** + * Replaces the validated private DNS servers in this {@code LinkProperties} with + * the given {@link Collection} of {@link InetAddress} objects. + * + * @param dnsServers The {@link Collection} of validated private DNS servers to set in this + * object. + * @hide + */ + @SystemApi + public void setValidatedPrivateDnsServers(@NonNull Collection dnsServers) { + mValidatedPrivateDnses.clear(); + for (InetAddress dnsServer: dnsServers) { + addValidatedPrivateDnsServer(dnsServer); + } + } + + /** + * Returns all the {@link InetAddress} for validated private DNS servers on this link. + * These are resolved from the private DNS server name. + * + * @return An unmodifiable {@link List} of {@link InetAddress} for validated private + * DNS servers on this link. + * @hide + */ + @SystemApi + public @NonNull List getValidatedPrivateDnsServers() { + return Collections.unmodifiableList(mValidatedPrivateDnses); + } + + /** + * Adds the given {@link InetAddress} to the list of PCSCF servers, if not present. + * + * @param pcscfServer The {@link InetAddress} to add to the list of PCSCF servers. + * @return true if the PCSCF server was added, false otherwise. + * @hide + */ + @SystemApi + public boolean addPcscfServer(@NonNull InetAddress pcscfServer) { + if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) { + mPcscfs.add(pcscfServer); + return true; + } + return false; + } + + /** + * Removes the given {@link InetAddress} from the list of PCSCF servers. + * + * @param pcscfServer The {@link InetAddress} to remove from the list of PCSCF servers. + * @return true if the PCSCF server was removed, false otherwise. + * @hide + */ + public boolean removePcscfServer(@NonNull InetAddress pcscfServer) { + return mPcscfs.remove(pcscfServer); + } + + /** + * Replaces the PCSCF servers in this {@code LinkProperties} with + * the given {@link Collection} of {@link InetAddress} objects. + * + * @param pcscfServers The {@link Collection} of PCSCF servers to set in this object. + * @hide + */ + @SystemApi + public void setPcscfServers(@NonNull Collection pcscfServers) { + mPcscfs.clear(); + for (InetAddress pcscfServer: pcscfServers) { + addPcscfServer(pcscfServer); + } + } + + /** + * Returns all the {@link InetAddress} for PCSCF servers on this link. + * + * @return An unmodifiable {@link List} of {@link InetAddress} for PCSCF servers on + * this link. + * @hide + */ + @SystemApi + public @NonNull List getPcscfServers() { + return Collections.unmodifiableList(mPcscfs); + } + + /** + * Sets the DNS domain search path used on this link. + * + * @param domains A {@link String} listing in priority order the comma separated + * domains to search when resolving host names on this link. + */ + public void setDomains(@Nullable String domains) { + mDomains = domains; + } + + /** + * Get the DNS domains search path set for this link. May be {@code null} if not set. + * + * @return A {@link String} containing the comma separated domains to search when resolving host + * names on this link or {@code null}. + */ + public @Nullable String getDomains() { + return mDomains; + } + + /** + * Sets the Maximum Transmission Unit size to use on this link. This should not be used + * unless the system default (1500) is incorrect. Values less than 68 or greater than + * 10000 will be ignored. + * + * @param mtu The MTU to use for this link. + */ + public void setMtu(int mtu) { + mMtu = mtu; + } + + /** + * Gets any non-default MTU size set for this link. Note that if the default is being used + * this will return 0. + * + * @return The mtu value set for this link. + */ + public int getMtu() { + return mMtu; + } + + /** + * Sets the tcp buffers sizes to be used when this link is the system default. + * Should be of the form "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max". + * + * @param tcpBufferSizes The tcp buffers sizes to use. + * + * @hide + */ + @SystemApi + public void setTcpBufferSizes(@Nullable String tcpBufferSizes) { + mTcpBufferSizes = tcpBufferSizes; + } + + /** + * Gets the tcp buffer sizes. May be {@code null} if not set. + * + * @return the tcp buffer sizes to use when this link is the system default or {@code null}. + * + * @hide + */ + @SystemApi + public @Nullable String getTcpBufferSizes() { + return mTcpBufferSizes; + } + + private RouteInfo routeWithInterface(RouteInfo route) { + return new RouteInfo( + route.getDestination(), + route.getGateway(), + mIfaceName, + route.getType(), + route.getMtu()); + } + + private int findRouteIndexByRouteKey(RouteInfo route) { + for (int i = 0; i < mRoutes.size(); i++) { + if (mRoutes.get(i).getRouteKey().equals(route.getRouteKey())) { + return i; + } + } + return -1; + } + + /** + * Adds a {@link RouteInfo} to this {@code LinkProperties}, if a {@link RouteInfo} + * with the same {@link RouteInfo.RouteKey} with different properties + * (e.g., different MTU), it will be updated. If the {@link RouteInfo} had an + * interface name set and that differs from the interface set for this + * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. + * The proper course is to add either un-named or properly named {@link RouteInfo}. + * + * @param route A {@link RouteInfo} to add to this object. + * @return {@code true} was added or updated, false otherwise. + */ + public boolean addRoute(@NonNull RouteInfo route) { + String routeIface = route.getInterface(); + if (routeIface != null && !routeIface.equals(mIfaceName)) { + throw new IllegalArgumentException( + "Route added with non-matching interface: " + routeIface + + " vs. " + mIfaceName); + } + route = routeWithInterface(route); + + int i = findRouteIndexByRouteKey(route); + if (i == -1) { + // Route was not present. Add it. + mRoutes.add(route); + return true; + } else if (mRoutes.get(i).equals(route)) { + // Route was present and has same properties. Do nothing. + return false; + } else { + // Route was present and has different properties. Update it. + mRoutes.set(i, route); + return true; + } + } + + /** + * Removes a {@link RouteInfo} from this {@code LinkProperties}, if present. The route must + * specify an interface and the interface must match the interface of this + * {@code LinkProperties}, or it will not be removed. + * + * @param route A {@link RouteInfo} specifying the route to remove. + * @return {@code true} if the route was removed, {@code false} if it was not present. + * + * @hide + */ + @SystemApi + public boolean removeRoute(@NonNull RouteInfo route) { + return Objects.equals(mIfaceName, route.getInterface()) && mRoutes.remove(route); + } + + /** + * Returns all the {@link RouteInfo} set on this link. + * + * @return An unmodifiable {@link List} of {@link RouteInfo} for this link. + */ + public @NonNull List getRoutes() { + return Collections.unmodifiableList(mRoutes); + } + + /** + * Make sure this LinkProperties instance contains routes that cover the local subnet + * of its link addresses. Add any route that is missing. + * @hide + */ + public void ensureDirectlyConnectedRoutes() { + for (LinkAddress addr : mLinkAddresses) { + addRoute(new RouteInfo(addr, null, mIfaceName)); + } + } + + /** + * Returns all the routes on this link and all the links stacked above it. + * @hide + */ + @SystemApi + public @NonNull List getAllRoutes() { + List routes = new ArrayList<>(mRoutes); + for (LinkProperties stacked: mStackedLinks.values()) { + routes.addAll(stacked.getAllRoutes()); + } + return routes; + } + + /** + * Sets the recommended {@link ProxyInfo} to use on this link, or {@code null} for none. + * Note that Http Proxies are only a hint - the system recommends their use, but it does + * not enforce it and applications may ignore them. + * + * @param proxy A {@link ProxyInfo} defining the HTTP Proxy to use on this link. + */ + public void setHttpProxy(@Nullable ProxyInfo proxy) { + mHttpProxy = proxy; + } + + /** + * Gets the recommended {@link ProxyInfo} (or {@code null}) set on this link. + * + * @return The {@link ProxyInfo} set on this link or {@code null}. + */ + public @Nullable ProxyInfo getHttpProxy() { + return mHttpProxy; + } + + /** + * Returns the NAT64 prefix in use on this link, if any. + * + * @return the NAT64 prefix or {@code null}. + */ + public @Nullable IpPrefix getNat64Prefix() { + return mNat64Prefix; + } + + /** + * Sets the NAT64 prefix in use on this link. + * + * Currently, only 96-bit prefixes (i.e., where the 32-bit IPv4 address is at the end of the + * 128-bit IPv6 address) are supported or {@code null} for no prefix. + * + * @param prefix the NAT64 prefix. + */ + public void setNat64Prefix(@Nullable IpPrefix prefix) { + if (prefix != null && prefix.getPrefixLength() != 96) { + throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix); + } + mNat64Prefix = prefix; // IpPrefix objects are immutable. + } + + /** + * Adds a stacked link. + * + * If there is already a stacked link with the same interface name as link, + * that link is replaced with link. Otherwise, link is added to the list + * of stacked links. + * + * @param link The link to add. + * @return true if the link was stacked, false otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean addStackedLink(@NonNull LinkProperties link) { + if (link.getInterfaceName() != null) { + mStackedLinks.put(link.getInterfaceName(), link); + return true; + } + return false; + } + + /** + * Removes a stacked link. + * + * If there is a stacked link with the given interface name, it is + * removed. Otherwise, nothing changes. + * + * @param iface The interface name of the link to remove. + * @return true if the link was removed, false otherwise. + * @hide + */ + public boolean removeStackedLink(@NonNull String iface) { + LinkProperties removed = mStackedLinks.remove(iface); + return removed != null; + } + + /** + * Returns all the links stacked on top of this link. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public @NonNull List getStackedLinks() { + if (mStackedLinks.isEmpty()) { + return Collections.emptyList(); + } + final List stacked = new ArrayList<>(); + for (LinkProperties link : mStackedLinks.values()) { + stacked.add(new LinkProperties(link)); + } + return Collections.unmodifiableList(stacked); + } + + /** + * Clears this object to its initial state. + */ + public void clear() { + if (mParcelSensitiveFields) { + throw new UnsupportedOperationException( + "Cannot clear LinkProperties when parcelSensitiveFields is set"); + } + + mIfaceName = null; + mLinkAddresses.clear(); + mDnses.clear(); + mUsePrivateDns = false; + mPrivateDnsServerName = null; + mPcscfs.clear(); + mDomains = null; + mRoutes.clear(); + mHttpProxy = null; + mStackedLinks.clear(); + mMtu = 0; + mDhcpServerAddress = null; + mTcpBufferSizes = null; + mNat64Prefix = null; + mWakeOnLanSupported = false; + mCaptivePortalApiUrl = null; + mCaptivePortalData = null; + } + + /** + * Implement the Parcelable interface + */ + public int describeContents() { + return 0; + } + + @Override + public String toString() { + // Space as a separator, so no need for spaces at start/end of the individual fragments. + final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}"); + + if (mIfaceName != null) { + resultJoiner.add("InterfaceName:"); + resultJoiner.add(mIfaceName); + } + + resultJoiner.add("LinkAddresses: ["); + if (!mLinkAddresses.isEmpty()) { + resultJoiner.add(TextUtils.join(",", mLinkAddresses)); + } + resultJoiner.add("]"); + + resultJoiner.add("DnsAddresses: ["); + if (!mDnses.isEmpty()) { + resultJoiner.add(TextUtils.join(",", mDnses)); + } + resultJoiner.add("]"); + + if (mUsePrivateDns) { + resultJoiner.add("UsePrivateDns: true"); + } + + if (mPrivateDnsServerName != null) { + resultJoiner.add("PrivateDnsServerName:"); + resultJoiner.add(mPrivateDnsServerName); + } + + if (!mPcscfs.isEmpty()) { + resultJoiner.add("PcscfAddresses: ["); + resultJoiner.add(TextUtils.join(",", mPcscfs)); + resultJoiner.add("]"); + } + + if (!mValidatedPrivateDnses.isEmpty()) { + final StringJoiner validatedPrivateDnsesJoiner = + new StringJoiner(",", "ValidatedPrivateDnsAddresses: [", "]"); + for (final InetAddress addr : mValidatedPrivateDnses) { + validatedPrivateDnsesJoiner.add(addr.getHostAddress()); + } + resultJoiner.add(validatedPrivateDnsesJoiner.toString()); + } + + resultJoiner.add("Domains:"); + resultJoiner.add(mDomains); + + resultJoiner.add("MTU:"); + resultJoiner.add(Integer.toString(mMtu)); + + if (mWakeOnLanSupported) { + resultJoiner.add("WakeOnLanSupported: true"); + } + + if (mDhcpServerAddress != null) { + resultJoiner.add("ServerAddress:"); + resultJoiner.add(mDhcpServerAddress.toString()); + } + + if (mCaptivePortalApiUrl != null) { + resultJoiner.add("CaptivePortalApiUrl: " + mCaptivePortalApiUrl); + } + + if (mCaptivePortalData != null) { + resultJoiner.add("CaptivePortalData: " + mCaptivePortalData); + } + + if (mTcpBufferSizes != null) { + resultJoiner.add("TcpBufferSizes:"); + resultJoiner.add(mTcpBufferSizes); + } + + resultJoiner.add("Routes: ["); + if (!mRoutes.isEmpty()) { + resultJoiner.add(TextUtils.join(",", mRoutes)); + } + resultJoiner.add("]"); + + if (mHttpProxy != null) { + resultJoiner.add("HttpProxy:"); + resultJoiner.add(mHttpProxy.toString()); + } + + if (mNat64Prefix != null) { + resultJoiner.add("Nat64Prefix:"); + resultJoiner.add(mNat64Prefix.toString()); + } + + final Collection stackedLinksValues = mStackedLinks.values(); + if (!stackedLinksValues.isEmpty()) { + final StringJoiner stackedLinksJoiner = new StringJoiner(",", "Stacked: [", "]"); + for (final LinkProperties lp : stackedLinksValues) { + stackedLinksJoiner.add("[ " + lp + " ]"); + } + resultJoiner.add(stackedLinksJoiner.toString()); + } + + return resultJoiner.toString(); + } + + /** + * Returns true if this link has an IPv4 address. + * + * @return {@code true} if there is an IPv4 address, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv4Address() { + for (LinkAddress address : mLinkAddresses) { + if (address.getAddress() instanceof Inet4Address) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv4 address, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv4Address() { + return hasIpv4Address(); + } + + /** + * Returns true if this link or any of its stacked interfaces has an IPv4 address. + * + * @return {@code true} if there is an IPv4 address, {@code false} otherwise. + */ + private boolean hasIpv4AddressOnInterface(String iface) { + // mIfaceName can be null. + return (Objects.equals(iface, mIfaceName) && hasIpv4Address()) + || (iface != null && mStackedLinks.containsKey(iface) + && mStackedLinks.get(iface).hasIpv4Address()); + } + + /** + * Returns true if this link has a global preferred IPv6 address. + * + * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasGlobalIpv6Address() { + for (LinkAddress address : mLinkAddresses) { + if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) { + return true; + } + } + return false; + } + + /** + * Returns true if this link has an IPv4 unreachable default route. + * + * @return {@code true} if there is an IPv4 unreachable default route, {@code false} otherwise. + * @hide + */ + public boolean hasIpv4UnreachableDefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv4UnreachableDefault()) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasGlobalIPv6Address() { + return hasGlobalIpv6Address(); + } + + /** + * Returns true if this link has an IPv4 default route. + * + * @return {@code true} if there is an IPv4 default route, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv4DefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv4Default()) { + return true; + } + } + return false; + } + + /** + * Returns true if this link has an IPv6 unreachable default route. + * + * @return {@code true} if there is an IPv6 unreachable default route, {@code false} otherwise. + * @hide + */ + public boolean hasIpv6UnreachableDefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv6UnreachableDefault()) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv4 default route, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv4DefaultRoute() { + return hasIpv4DefaultRoute(); + } + + /** + * Returns true if this link has an IPv6 default route. + * + * @return {@code true} if there is an IPv6 default route, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv6DefaultRoute() { + for (RouteInfo r : mRoutes) { + if (r.isIPv6Default()) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv6 default route, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv6DefaultRoute() { + return hasIpv6DefaultRoute(); + } + + /** + * Returns true if this link has an IPv4 DNS server. + * + * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv4DnsServer() { + for (InetAddress ia : mDnses) { + if (ia instanceof Inet4Address) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv4DnsServer() { + return hasIpv4DnsServer(); + } + + /** + * Returns true if this link has an IPv6 DNS server. + * + * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean hasIpv6DnsServer() { + for (InetAddress ia : mDnses) { + if (ia instanceof Inet6Address) { + return true; + } + } + return false; + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean hasIPv6DnsServer() { + return hasIpv6DnsServer(); + } + + /** + * Returns true if this link has an IPv4 PCSCF server. + * + * @return {@code true} if there is an IPv4 PCSCF server, {@code false} otherwise. + * @hide + */ + public boolean hasIpv4PcscfServer() { + for (InetAddress ia : mPcscfs) { + if (ia instanceof Inet4Address) { + return true; + } + } + return false; + } + + /** + * Returns true if this link has an IPv6 PCSCF server. + * + * @return {@code true} if there is an IPv6 PCSCF server, {@code false} otherwise. + * @hide + */ + public boolean hasIpv6PcscfServer() { + for (InetAddress ia : mPcscfs) { + if (ia instanceof Inet6Address) { + return true; + } + } + return false; + } + + /** + * Returns true if this link is provisioned for global IPv4 connectivity. + * This requires an IP address, default route, and DNS server. + * + * @return {@code true} if the link is provisioned, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean isIpv4Provisioned() { + return (hasIpv4Address() + && hasIpv4DefaultRoute() + && hasIpv4DnsServer()); + } + + /** + * Returns true if this link is provisioned for global IPv6 connectivity. + * This requires an IP address, default route, and DNS server. + * + * @return {@code true} if the link is provisioned, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean isIpv6Provisioned() { + return (hasGlobalIpv6Address() + && hasIpv6DefaultRoute() + && hasIpv6DnsServer()); + } + + /** + * For backward compatibility. + * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely + * just yet. + * @return {@code true} if the link is provisioned, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + public boolean isIPv6Provisioned() { + return isIpv6Provisioned(); + } + + + /** + * Returns true if this link is provisioned for global connectivity, + * for at least one Internet Protocol family. + * + * @return {@code true} if the link is provisioned, {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean isProvisioned() { + return (isIpv4Provisioned() || isIpv6Provisioned()); + } + + /** + * Evaluate whether the {@link InetAddress} is considered reachable. + * + * @return {@code true} if the given {@link InetAddress} is considered reachable, + * {@code false} otherwise. + * @hide + */ + @SystemApi + public boolean isReachable(@NonNull InetAddress ip) { + final List allRoutes = getAllRoutes(); + // If we don't have a route to this IP address, it's not reachable. + final RouteInfo bestRoute = RouteInfo.selectBestRoute(allRoutes, ip); + if (bestRoute == null) { + return false; + } + + // TODO: better source address evaluation for destination addresses. + + if (ip instanceof Inet4Address) { + // For IPv4, it suffices for now to simply have any address. + return hasIpv4AddressOnInterface(bestRoute.getInterface()); + } else if (ip instanceof Inet6Address) { + if (ip.isLinkLocalAddress()) { + // For now, just make sure link-local destinations have + // scopedIds set, since transmits will generally fail otherwise. + // TODO: verify it matches the ifindex of one of the interfaces. + return (((Inet6Address)ip).getScopeId() != 0); + } else { + // For non-link-local destinations check that either the best route + // is directly connected or that some global preferred address exists. + // TODO: reconsider all cases (disconnected ULA networks, ...). + return (!bestRoute.hasGateway() || hasGlobalIpv6Address()); + } + } + + return false; + } + + /** + * Compares this {@code LinkProperties} interface name against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalInterfaceName(target, this); + } + + /** + * Compares this {@code LinkProperties} DHCP server address against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalDhcpServerAddress(@NonNull LinkProperties target) { + return Objects.equals(mDhcpServerAddress, target.mDhcpServerAddress); + } + + /** + * Compares this {@code LinkProperties} interface addresses against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean isIdenticalAddresses(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalAddresses(target, this); + } + + /** + * Compares this {@code LinkProperties} DNS addresses against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean isIdenticalDnses(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalDnses(target, this); + } + + /** + * Compares this {@code LinkProperties} private DNS settings against the + * target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalPrivateDns(@NonNull LinkProperties target) { + return (isPrivateDnsActive() == target.isPrivateDnsActive() + && TextUtils.equals(getPrivateDnsServerName(), + target.getPrivateDnsServerName())); + } + + /** + * Compares this {@code LinkProperties} validated private DNS addresses against + * the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalValidatedPrivateDnses(@NonNull LinkProperties target) { + Collection targetDnses = target.getValidatedPrivateDnsServers(); + return (mValidatedPrivateDnses.size() == targetDnses.size()) + ? mValidatedPrivateDnses.containsAll(targetDnses) : false; + } + + /** + * Compares this {@code LinkProperties} PCSCF addresses against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalPcscfs(@NonNull LinkProperties target) { + Collection targetPcscfs = target.getPcscfServers(); + return (mPcscfs.size() == targetPcscfs.size()) ? + mPcscfs.containsAll(targetPcscfs) : false; + } + + /** + * Compares this {@code LinkProperties} Routes against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage + public boolean isIdenticalRoutes(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalRoutes(target, this); + } + + /** + * Compares this {@code LinkProperties} HttpProxy against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) { + return LinkPropertiesUtils.isIdenticalHttpProxy(target, this); + } + + /** + * Compares this {@code LinkProperties} stacked links against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public boolean isIdenticalStackedLinks(@NonNull LinkProperties target) { + if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) { + return false; + } + for (LinkProperties stacked : mStackedLinks.values()) { + // Hashtable values can never be null. + String iface = stacked.getInterfaceName(); + if (!stacked.equals(target.mStackedLinks.get(iface))) { + return false; + } + } + return true; + } + + /** + * Compares this {@code LinkProperties} MTU against the target + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalMtu(@NonNull LinkProperties target) { + return getMtu() == target.getMtu(); + } + + /** + * Compares this {@code LinkProperties} Tcp buffer sizes against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalTcpBufferSizes(@NonNull LinkProperties target) { + return Objects.equals(mTcpBufferSizes, target.mTcpBufferSizes); + } + + /** + * Compares this {@code LinkProperties} NAT64 prefix against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalNat64Prefix(@NonNull LinkProperties target) { + return Objects.equals(mNat64Prefix, target.mNat64Prefix); + } + + /** + * Compares this {@code LinkProperties} WakeOnLan supported against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalWakeOnLan(LinkProperties target) { + return isWakeOnLanSupported() == target.isWakeOnLanSupported(); + } + + /** + * Compares this {@code LinkProperties}'s CaptivePortalApiUrl against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalCaptivePortalApiUrl(LinkProperties target) { + return Objects.equals(mCaptivePortalApiUrl, target.mCaptivePortalApiUrl); + } + + /** + * Compares this {@code LinkProperties}'s CaptivePortalData against the target. + * + * @param target LinkProperties to compare. + * @return {@code true} if both are identical, {@code false} otherwise. + * @hide + */ + public boolean isIdenticalCaptivePortalData(LinkProperties target) { + return Objects.equals(mCaptivePortalData, target.mCaptivePortalData); + } + + /** + * Set whether the network interface supports WakeOnLAN + * + * @param supported WakeOnLAN supported value + * + * @hide + */ + public void setWakeOnLanSupported(boolean supported) { + mWakeOnLanSupported = supported; + } + + /** + * Returns whether the network interface supports WakeOnLAN + * + * @return {@code true} if interface supports WakeOnLAN, {@code false} otherwise. + */ + public boolean isWakeOnLanSupported() { + return mWakeOnLanSupported; + } + + /** + * Set the URL of the captive portal API endpoint to get more information about the network. + * @hide + */ + @SystemApi + public void setCaptivePortalApiUrl(@Nullable Uri url) { + mCaptivePortalApiUrl = url; + } + + /** + * Get the URL of the captive portal API endpoint to get more information about the network. + * + *

This is null unless the application has + * {@link android.Manifest.permission.NETWORK_SETTINGS} or + * {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions, and the network provided + * the URL. + * @hide + */ + @SystemApi + @Nullable + public Uri getCaptivePortalApiUrl() { + return mCaptivePortalApiUrl; + } + + /** + * Set the CaptivePortalData obtained from the captive portal API (RFC7710bis). + * @hide + */ + @SystemApi + public void setCaptivePortalData(@Nullable CaptivePortalData data) { + mCaptivePortalData = data; + } + + /** + * Get the CaptivePortalData obtained from the captive portal API (RFC7710bis). + * + *

This is null unless the application has + * {@link android.Manifest.permission.NETWORK_SETTINGS} or + * {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions. + * @hide + */ + @SystemApi + @Nullable + public CaptivePortalData getCaptivePortalData() { + return mCaptivePortalData; + } + + /** + * Compares this {@code LinkProperties} instance against the target + * LinkProperties in {@code obj}. Two LinkPropertieses are equal if + * all their fields are equal in values. + * + * For collection fields, such as mDnses, containsAll() is used to check + * if two collections contains the same elements, independent of order. + * There are two thoughts regarding containsAll() + * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal. + * 2. Worst case performance is O(n^2). + * + * @param obj the object to be tested for equality. + * @return {@code true} if both objects are equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + + if (!(obj instanceof LinkProperties)) return false; + + LinkProperties target = (LinkProperties) obj; + /* + * This method does not check that stacked interfaces are equal, because + * stacked interfaces are not so much a property of the link as a + * description of connections between links. + */ + return isIdenticalInterfaceName(target) + && isIdenticalAddresses(target) + && isIdenticalDhcpServerAddress(target) + && isIdenticalDnses(target) + && isIdenticalPrivateDns(target) + && isIdenticalValidatedPrivateDnses(target) + && isIdenticalPcscfs(target) + && isIdenticalRoutes(target) + && isIdenticalHttpProxy(target) + && isIdenticalStackedLinks(target) + && isIdenticalMtu(target) + && isIdenticalTcpBufferSizes(target) + && isIdenticalNat64Prefix(target) + && isIdenticalWakeOnLan(target) + && isIdenticalCaptivePortalApiUrl(target) + && isIdenticalCaptivePortalData(target); + } + + /** + * Generate hashcode based on significant fields + * + * Equal objects must produce the same hash code, while unequal objects + * may have the same hash codes. + */ + @Override + public int hashCode() { + return ((null == mIfaceName) ? 0 : mIfaceName.hashCode() + + mLinkAddresses.size() * 31 + + mDnses.size() * 37 + + mValidatedPrivateDnses.size() * 61 + + ((null == mDomains) ? 0 : mDomains.hashCode()) + + mRoutes.size() * 41 + + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()) + + mStackedLinks.hashCode() * 47) + + mMtu * 51 + + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode()) + + (mUsePrivateDns ? 57 : 0) + + ((null == mDhcpServerAddress) ? 0 : mDhcpServerAddress.hashCode()) + + mPcscfs.size() * 67 + + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode()) + + Objects.hash(mNat64Prefix) + + (mWakeOnLanSupported ? 71 : 0) + + Objects.hash(mCaptivePortalApiUrl, mCaptivePortalData); + } + + /** + * Implement the Parcelable interface. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getInterfaceName()); + dest.writeInt(mLinkAddresses.size()); + for (LinkAddress linkAddress : mLinkAddresses) { + dest.writeParcelable(linkAddress, flags); + } + + writeAddresses(dest, mDnses); + writeAddresses(dest, mValidatedPrivateDnses); + dest.writeBoolean(mUsePrivateDns); + dest.writeString(mPrivateDnsServerName); + writeAddresses(dest, mPcscfs); + dest.writeString(mDomains); + writeAddress(dest, mDhcpServerAddress); + dest.writeInt(mMtu); + dest.writeString(mTcpBufferSizes); + dest.writeInt(mRoutes.size()); + for (RouteInfo route : mRoutes) { + dest.writeParcelable(route, flags); + } + + if (mHttpProxy != null) { + dest.writeByte((byte)1); + dest.writeParcelable(mHttpProxy, flags); + } else { + dest.writeByte((byte)0); + } + dest.writeParcelable(mNat64Prefix, 0); + + ArrayList stackedLinks = new ArrayList<>(mStackedLinks.values()); + dest.writeList(stackedLinks); + + dest.writeBoolean(mWakeOnLanSupported); + dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalApiUrl : null, 0); + dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalData : null, 0); + } + + private static void writeAddresses(@NonNull Parcel dest, @NonNull List list) { + dest.writeInt(list.size()); + for (InetAddress d : list) { + writeAddress(dest, d); + } + } + + private static void writeAddress(@NonNull Parcel dest, @Nullable InetAddress addr) { + byte[] addressBytes = (addr == null ? null : addr.getAddress()); + dest.writeByteArray(addressBytes); + if (addr instanceof Inet6Address) { + final Inet6Address v6Addr = (Inet6Address) addr; + final boolean hasScopeId = v6Addr.getScopeId() != 0; + dest.writeBoolean(hasScopeId); + if (hasScopeId) dest.writeInt(v6Addr.getScopeId()); + } + } + + @Nullable + private static InetAddress readAddress(@NonNull Parcel p) throws UnknownHostException { + final byte[] addr = p.createByteArray(); + if (addr == null) return null; + + if (addr.length == INET6_ADDR_LENGTH) { + final boolean hasScopeId = p.readBoolean(); + final int scopeId = hasScopeId ? p.readInt() : 0; + return Inet6Address.getByAddress(null /* host */, addr, scopeId); + } + + return InetAddress.getByAddress(addr); + } + + /** + * Implement the Parcelable interface. + */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public LinkProperties createFromParcel(Parcel in) { + LinkProperties netProp = new LinkProperties(); + + String iface = in.readString(); + if (iface != null) { + netProp.setInterfaceName(iface); + } + int addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + netProp.addLinkAddress(in.readParcelable(null)); + } + addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + try { + netProp.addDnsServer(readAddress(in)); + } catch (UnknownHostException e) { } + } + addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + try { + netProp.addValidatedPrivateDnsServer(readAddress(in)); + } catch (UnknownHostException e) { } + } + netProp.setUsePrivateDns(in.readBoolean()); + netProp.setPrivateDnsServerName(in.readString()); + addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + try { + netProp.addPcscfServer(readAddress(in)); + } catch (UnknownHostException e) { } + } + netProp.setDomains(in.readString()); + try { + netProp.setDhcpServerAddress((Inet4Address) InetAddress + .getByAddress(in.createByteArray())); + } catch (UnknownHostException e) { } + netProp.setMtu(in.readInt()); + netProp.setTcpBufferSizes(in.readString()); + addressCount = in.readInt(); + for (int i = 0; i < addressCount; i++) { + netProp.addRoute(in.readParcelable(null)); + } + if (in.readByte() == 1) { + netProp.setHttpProxy(in.readParcelable(null)); + } + netProp.setNat64Prefix(in.readParcelable(null)); + ArrayList stackedLinks = new ArrayList(); + in.readList(stackedLinks, LinkProperties.class.getClassLoader()); + for (LinkProperties stackedLink: stackedLinks) { + netProp.addStackedLink(stackedLink); + } + netProp.setWakeOnLanSupported(in.readBoolean()); + + netProp.setCaptivePortalApiUrl(in.readParcelable(null)); + netProp.setCaptivePortalData(in.readParcelable(null)); + return netProp; + } + + public LinkProperties[] newArray(int size) { + return new LinkProperties[size]; + } + }; + + /** + * Check the valid MTU range based on IPv4 or IPv6. + * @hide + */ + public static boolean isValidMtu(int mtu, boolean ipv6) { + if (ipv6) { + return mtu >= MIN_MTU_V6 && mtu <= MAX_MTU; + } else { + return mtu >= MIN_MTU && mtu <= MAX_MTU; + } + } +} diff --git a/framework/src/android/net/MacAddress.aidl b/framework/src/android/net/MacAddress.aidl new file mode 100644 index 0000000000..48a18a7ac8 --- /dev/null +++ b/framework/src/android/net/MacAddress.aidl @@ -0,0 +1,20 @@ +/** + * + * 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; + +@JavaOnlyStableParcelable parcelable MacAddress; diff --git a/framework/src/android/net/MacAddress.java b/framework/src/android/net/MacAddress.java new file mode 100644 index 0000000000..c7116b41e8 --- /dev/null +++ b/framework/src/android/net/MacAddress.java @@ -0,0 +1,400 @@ +/* + * Copyright 2017 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; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.net.wifi.WifiInfo; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; +import com.android.net.module.util.MacAddressUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.Inet6Address; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.Arrays; + +/** + * Representation of a MAC address. + * + * This class only supports 48 bits long addresses and does not support 64 bits long addresses. + * Instances of this class are immutable. This class provides implementations of hashCode() + * and equals() that make it suitable for use as keys in standard implementations of + * {@link java.util.Map}. + */ +public final class MacAddress implements Parcelable { + + private static final int ETHER_ADDR_LEN = 6; + private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + + /** + * The MacAddress representing the unique broadcast MAC address. + */ + public static final MacAddress BROADCAST_ADDRESS = MacAddress.fromBytes(ETHER_ADDR_BROADCAST); + + /** + * The MacAddress zero MAC address. + * + *

Not publicly exposed or treated specially since the OUI 00:00:00 is registered. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0); + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_UNKNOWN, + TYPE_UNICAST, + TYPE_MULTICAST, + TYPE_BROADCAST, + }) + public @interface MacAddressType { } + + /** @hide Indicates a MAC address of unknown type. */ + public static final int TYPE_UNKNOWN = 0; + /** Indicates a MAC address is a unicast address. */ + public static final int TYPE_UNICAST = 1; + /** Indicates a MAC address is a multicast address. */ + public static final int TYPE_MULTICAST = 2; + /** Indicates a MAC address is the broadcast address. */ + public static final int TYPE_BROADCAST = 3; + + private static final long VALID_LONG_MASK = (1L << 48) - 1; + private static final long LOCALLY_ASSIGNED_MASK = MacAddress.fromString("2:0:0:0:0:0").mAddr; + private static final long MULTICAST_MASK = MacAddress.fromString("1:0:0:0:0:0").mAddr; + private static final long OUI_MASK = MacAddress.fromString("ff:ff:ff:0:0:0").mAddr; + private static final long NIC_MASK = MacAddress.fromString("0:0:0:ff:ff:ff").mAddr; + private static final MacAddress BASE_GOOGLE_MAC = MacAddress.fromString("da:a1:19:0:0:0"); + /** Default wifi MAC address used for a special purpose **/ + private static final MacAddress DEFAULT_MAC_ADDRESS = + MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); + + // Internal representation of the MAC address as a single 8 byte long. + // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the + // MAC address are encoded in the 6 least significant bytes of the long, where the first + // byte of the array is mapped to the 3rd highest logical byte of the long, the second + // byte of the array is mapped to the 4th highest logical byte of the long, and so on. + private final long mAddr; + + private MacAddress(long addr) { + mAddr = (VALID_LONG_MASK & addr); + } + + /** + * Returns the type of this address. + * + * @return the int constant representing the MAC address type of this MacAddress. + */ + public @MacAddressType int getAddressType() { + if (equals(BROADCAST_ADDRESS)) { + return TYPE_BROADCAST; + } + if ((mAddr & MULTICAST_MASK) != 0) { + return TYPE_MULTICAST; + } + return TYPE_UNICAST; + } + + /** + * @return true if this MacAddress is a locally assigned address. + */ + public boolean isLocallyAssigned() { + return (mAddr & LOCALLY_ASSIGNED_MASK) != 0; + } + + /** + * Convert this MacAddress to a byte array. + * + * The returned array is in network order. For example, if this MacAddress is 1:2:3:4:5:6, + * the returned array is [1, 2, 3, 4, 5, 6]. + * + * @return a byte array representation of this MacAddress. + */ + public @NonNull byte[] toByteArray() { + return byteAddrFromLongAddr(mAddr); + } + + /** + * Returns a human-readable representation of this MacAddress. + * The exact format is implementation-dependent and should not be assumed to have any + * particular format. + */ + @Override + public @NonNull String toString() { + return stringAddrFromLongAddr(mAddr); + } + + /** + * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal + * numbers in [0,ff] joined by ':' characters. + */ + public @NonNull String toOuiString() { + return String.format( + "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff); + } + + @Override + public int hashCode() { + return (int) ((mAddr >> 32) ^ mAddr); + } + + @Override + public boolean equals(Object o) { + return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mAddr); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public MacAddress createFromParcel(Parcel in) { + return new MacAddress(in.readLong()); + } + + public MacAddress[] newArray(int size) { + return new MacAddress[size]; + } + }; + + /** + * Returns true if the given byte array is an valid MAC address. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array. + * @return true if the given byte array is not null and has the length of a MAC address. + * + * @hide + */ + public static boolean isMacAddress(byte[] addr) { + return MacAddressUtils.isMacAddress(addr); + } + + /** + * Returns the MAC address type of the MAC address represented by the given byte array, + * or null if the given byte array does not represent a MAC address. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array representing a MAC address. + * @return the int constant representing the MAC address type of the MAC address represented + * by the given byte array, or type UNKNOWN if the byte array is not a valid MAC address. + * + * @hide + */ + public static int macAddressType(byte[] addr) { + if (!isMacAddress(addr)) { + return TYPE_UNKNOWN; + } + return MacAddress.fromBytes(addr).getAddressType(); + } + + /** + * Converts a String representation of a MAC address to a byte array representation. + * A valid String representation for a MacAddress is a series of 6 values in the + * range [0,ff] printed in hexadecimal and joined by ':' characters. + * + * @param addr a String representation of a MAC address. + * @return the byte representation of the MAC address. + * @throws IllegalArgumentException if the given String is not a valid representation. + * + * @hide + */ + public static @NonNull byte[] byteAddrFromStringAddr(String addr) { + Preconditions.checkNotNull(addr); + String[] parts = addr.split(":"); + if (parts.length != ETHER_ADDR_LEN) { + throw new IllegalArgumentException(addr + " was not a valid MAC address"); + } + byte[] bytes = new byte[ETHER_ADDR_LEN]; + for (int i = 0; i < ETHER_ADDR_LEN; i++) { + int x = Integer.valueOf(parts[i], 16); + if (x < 0 || 0xff < x) { + throw new IllegalArgumentException(addr + "was not a valid MAC address"); + } + bytes[i] = (byte) x; + } + return bytes; + } + + /** + * Converts a byte array representation of a MAC address to a String representation made + * of 6 hexadecimal numbers in [0,ff] joined by ':' characters. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array representation of a MAC address. + * @return the String representation of the MAC address. + * @throws IllegalArgumentException if the given byte array is not a valid representation. + * + * @hide + */ + public static @NonNull String stringAddrFromByteAddr(byte[] addr) { + if (!isMacAddress(addr)) { + return null; + } + return String.format("%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + } + + private static byte[] byteAddrFromLongAddr(long addr) { + return MacAddressUtils.byteAddrFromLongAddr(addr); + } + + private static long longAddrFromByteAddr(byte[] addr) { + return MacAddressUtils.longAddrFromByteAddr(addr); + } + + // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr)) + // that avoids the allocation of an intermediary byte[]. + private static long longAddrFromStringAddr(String addr) { + Preconditions.checkNotNull(addr); + String[] parts = addr.split(":"); + if (parts.length != ETHER_ADDR_LEN) { + throw new IllegalArgumentException(addr + " was not a valid MAC address"); + } + long longAddr = 0; + for (int i = 0; i < parts.length; i++) { + int x = Integer.valueOf(parts[i], 16); + if (x < 0 || 0xff < x) { + throw new IllegalArgumentException(addr + "was not a valid MAC address"); + } + longAddr = x + (longAddr << 8); + } + return longAddr; + } + + // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr)) + // that avoids the allocation of an intermediary byte[]. + private static @NonNull String stringAddrFromLongAddr(long addr) { + return String.format("%02x:%02x:%02x:%02x:%02x:%02x", + (addr >> 40) & 0xff, + (addr >> 32) & 0xff, + (addr >> 24) & 0xff, + (addr >> 16) & 0xff, + (addr >> 8) & 0xff, + addr & 0xff); + } + + /** + * Creates a MacAddress from the given String representation. A valid String representation + * for a MacAddress is a series of 6 values in the range [0,ff] printed in hexadecimal + * and joined by ':' characters. + * + * @param addr a String representation of a MAC address. + * @return the MacAddress corresponding to the given String representation. + * @throws IllegalArgumentException if the given String is not a valid representation. + */ + public static @NonNull MacAddress fromString(@NonNull String addr) { + return new MacAddress(longAddrFromStringAddr(addr)); + } + + /** + * Creates a MacAddress from the given byte array representation. + * A valid byte array representation for a MacAddress is a non-null array of length 6. + * + * @param addr a byte array representation of a MAC address. + * @return the MacAddress corresponding to the given byte array representation. + * @throws IllegalArgumentException if the given byte array is not a valid representation. + */ + public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) { + return new MacAddress(longAddrFromByteAddr(addr)); + } + + /** + * Returns a generated MAC address whose 24 least significant bits constituting the + * NIC part of the address are randomly selected and has Google OUI base. + * + * The locally assigned bit is always set to 1. The multicast bit is always set to 0. + * + * @return a random locally assigned, unicast MacAddress with Google OUI. + * + * @hide + */ + public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() { + return MacAddressUtils.createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom()); + } + + // Convenience function for working around the lack of byte literals. + private static byte[] addr(int... in) { + if (in.length != ETHER_ADDR_LEN) { + throw new IllegalArgumentException(Arrays.toString(in) + + " was not an array with length equal to " + ETHER_ADDR_LEN); + } + byte[] out = new byte[ETHER_ADDR_LEN]; + for (int i = 0; i < ETHER_ADDR_LEN; i++) { + out[i] = (byte) in[i]; + } + return out; + } + + /** + * Checks if this MAC Address matches the provided range. + * + * @param baseAddress MacAddress representing the base address to compare with. + * @param mask MacAddress representing the mask to use during comparison. + * @return true if this MAC Address matches the given range. + * + */ + public boolean matches(@NonNull MacAddress baseAddress, @NonNull MacAddress mask) { + Preconditions.checkNotNull(baseAddress); + Preconditions.checkNotNull(mask); + return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr); + } + + /** + * Create a link-local Inet6Address from the MAC address. The EUI-48 MAC address is converted + * to an EUI-64 MAC address per RFC 4291. The resulting EUI-64 is used to construct a link-local + * IPv6 address per RFC 4862. + * + * @return A link-local Inet6Address constructed from the MAC address. + */ + public @Nullable Inet6Address getLinkLocalIpv6FromEui48Mac() { + byte[] macEui48Bytes = toByteArray(); + byte[] addr = new byte[16]; + + addr[0] = (byte) 0xfe; + addr[1] = (byte) 0x80; + addr[8] = (byte) (macEui48Bytes[0] ^ (byte) 0x02); // flip the link-local bit + addr[9] = macEui48Bytes[1]; + addr[10] = macEui48Bytes[2]; + addr[11] = (byte) 0xff; + addr[12] = (byte) 0xfe; + addr[13] = macEui48Bytes[3]; + addr[14] = macEui48Bytes[4]; + addr[15] = macEui48Bytes[5]; + + try { + return Inet6Address.getByAddress(null, addr, 0); + } catch (UnknownHostException e) { + return null; + } + } +} diff --git a/framework/src/android/net/NattKeepalivePacketData.java b/framework/src/android/net/NattKeepalivePacketData.java new file mode 100644 index 0000000000..c4f8fc281f --- /dev/null +++ b/framework/src/android/net/NattKeepalivePacketData.java @@ -0,0 +1,144 @@ +/* + * 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; + +import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS; +import static android.net.InvalidPacketException.ERROR_INVALID_PORT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.system.OsConstants; + +import com.android.net.module.util.IpUtils; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** @hide */ +@SystemApi +public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable { + private static final int IPV4_HEADER_LENGTH = 20; + private static final int UDP_HEADER_LENGTH = 8; + + // This should only be constructed via static factory methods, such as + // nattKeepalivePacket + public NattKeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort, + @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data) throws + InvalidPacketException { + super(srcAddress, srcPort, dstAddress, dstPort, data); + } + + /** + * Factory method to create Nat-T keepalive packet structure. + * @hide + */ + public static NattKeepalivePacketData nattKeepalivePacket( + InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) + throws InvalidPacketException { + + if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); + } + + if (dstPort != NattSocketKeepalive.NATT_PORT) { + throw new InvalidPacketException(ERROR_INVALID_PORT); + } + + int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1; + ByteBuffer buf = ByteBuffer.allocate(length); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort((short) 0x4500); // IP version and TOS + buf.putShort((short) length); + buf.putInt(0); // ID, flags, offset + buf.put((byte) 64); // TTL + buf.put((byte) OsConstants.IPPROTO_UDP); + int ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // IP checksum + buf.put(srcAddress.getAddress()); + buf.put(dstAddress.getAddress()); + buf.putShort((short) srcPort); + buf.putShort((short) dstPort); + buf.putShort((short) (length - 20)); // UDP length + int udpChecksumOffset = buf.position(); + buf.putShort((short) 0); // UDP checksum + buf.put((byte) 0xff); // NAT-T keepalive + buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); + buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH)); + + return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); + } + + /** Parcelable Implementation */ + public int describeContents() { + return 0; + } + + /** Write to parcel */ + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(getSrcAddress().getHostAddress()); + out.writeString(getDstAddress().getHostAddress()); + out.writeInt(getSrcPort()); + out.writeInt(getDstPort()); + } + + /** Parcelable Creator */ + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public NattKeepalivePacketData createFromParcel(Parcel in) { + final InetAddress srcAddress = + InetAddresses.parseNumericAddress(in.readString()); + final InetAddress dstAddress = + InetAddresses.parseNumericAddress(in.readString()); + final int srcPort = in.readInt(); + final int dstPort = in.readInt(); + try { + return NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort, + dstAddress, dstPort); + } catch (InvalidPacketException e) { + throw new IllegalArgumentException( + "Invalid NAT-T keepalive data: " + e.getError()); + } + } + + public NattKeepalivePacketData[] newArray(int size) { + return new NattKeepalivePacketData[size]; + } + }; + + @Override + public boolean equals(@Nullable final Object o) { + if (!(o instanceof NattKeepalivePacketData)) return false; + final NattKeepalivePacketData other = (NattKeepalivePacketData) o; + final InetAddress srcAddress = getSrcAddress(); + final InetAddress dstAddress = getDstAddress(); + return srcAddress.equals(other.getSrcAddress()) + && dstAddress.equals(other.getDstAddress()) + && getSrcPort() == other.getSrcPort() + && getDstPort() == other.getDstPort(); + } + + @Override + public int hashCode() { + return Objects.hash(getSrcAddress(), getDstAddress(), getSrcPort(), getDstPort()); + } +} diff --git a/framework/src/android/net/NattSocketKeepalive.java b/framework/src/android/net/NattSocketKeepalive.java new file mode 100644 index 0000000000..a15d165e65 --- /dev/null +++ b/framework/src/android/net/NattSocketKeepalive.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 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; + +import android.annotation.NonNull; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; + +import java.net.InetAddress; +import java.util.concurrent.Executor; + +/** @hide */ +public final class NattSocketKeepalive extends SocketKeepalive { + /** The NAT-T destination port for IPsec */ + public static final int NATT_PORT = 4500; + + @NonNull private final InetAddress mSource; + @NonNull private final InetAddress mDestination; + private final int mResourceId; + + NattSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + int resourceId, + @NonNull InetAddress source, + @NonNull InetAddress destination, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, pfd, executor, callback); + mSource = source; + mDestination = destination; + mResourceId = resourceId; + } + + @Override + void startImpl(int intervalSec) { + mExecutor.execute(() -> { + try { + mService.startNattKeepaliveWithFd(mNetwork, mPfd, mResourceId, + intervalSec, mCallback, + mSource.getHostAddress(), mDestination.getHostAddress()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting socket keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } + + @Override + void stopImpl() { + mExecutor.execute(() -> { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping socket keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } +} diff --git a/framework/src/android/net/Network.aidl b/framework/src/android/net/Network.aidl new file mode 100644 index 0000000000..05622025bf --- /dev/null +++ b/framework/src/android/net/Network.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright (C) 2014 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; + +@JavaOnlyStableParcelable parcelable Network; diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java new file mode 100644 index 0000000000..b07bd68a0f --- /dev/null +++ b/framework/src/android/net/Network.java @@ -0,0 +1,535 @@ +/* + * Copyright (C) 2014 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; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.okhttp.internalandroidapi.Dns; +import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory; + +import libcore.io.IoUtils; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import javax.net.SocketFactory; + +/** + * Identifies a {@code Network}. This is supplied to applications via + * {@link ConnectivityManager.NetworkCallback} in response to the active + * {@link ConnectivityManager#requestNetwork} or passive + * {@link ConnectivityManager#registerNetworkCallback} calls. + * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis + * through a targeted {@link SocketFactory} or process-wide via + * {@link ConnectivityManager#bindProcessToNetwork}. + */ +public class Network implements Parcelable { + + /** + * The unique id of the network. + * @hide + */ + @UnsupportedAppUsage + public final int netId; + + // Objects used to perform per-network operations such as getSocketFactory + // and openConnection, and a lock to protect access to them. + private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null; + // mUrlConnectionFactory is initialized lazily when it is first needed. + @GuardedBy("mLock") + private HttpURLConnectionFactory mUrlConnectionFactory; + private final Object mLock = new Object(); + + // Default connection pool values. These are evaluated at startup, just + // like the OkHttp code. Also like the OkHttp code, we will throw parse + // exceptions at class loading time if the properties are set but are not + // valid integers. + private static final boolean httpKeepAlive = + Boolean.parseBoolean(System.getProperty("http.keepAlive", "true")); + private static final int httpMaxConnections = + httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0; + private static final long httpKeepAliveDurationMs = + Long.parseLong(System.getProperty("http.keepAliveDuration", "300000")); // 5 minutes. + // Value used to obfuscate network handle longs. + // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // value in the native/android/net.c NDK implementation. + private static final long HANDLE_MAGIC = 0xcafed00dL; + private static final int HANDLE_MAGIC_SIZE = 32; + + // A boolean to control how getAllByName()/getByName() behaves in the face + // of Private DNS. + // + // When true, these calls will request that DNS resolution bypass any + // Private DNS that might otherwise apply. Use of this feature is restricted + // and permission checks are made by netd (attempts to bypass Private DNS + // without appropriate permission are silently turned into vanilla DNS + // requests). This only affects DNS queries made using this network object. + // + // It it not parceled to receivers because (a) it can be set or cleared at + // anytime and (b) receivers should be explicit about attempts to bypass + // Private DNS so that the intent of the code is easily determined and + // code search audits are possible. + private final transient boolean mPrivateDnsBypass; + + /** + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public Network(int netId) { + this(netId, false); + } + + /** + * @hide + */ + public Network(int netId, boolean privateDnsBypass) { + this.netId = netId; + this.mPrivateDnsBypass = privateDnsBypass; + } + + /** + * @hide + */ + @SystemApi + public Network(@NonNull Network that) { + this(that.netId, that.mPrivateDnsBypass); + } + + /** + * Operates the same as {@code InetAddress.getAllByName} except that host + * resolution is done on this network. + * + * @param host the hostname or literal IP string to be resolved. + * @return the array of addresses associated with the specified host. + * @throws UnknownHostException if the address lookup fails. + */ + public InetAddress[] getAllByName(String host) throws UnknownHostException { + return InetAddress.getAllByNameOnNet(host, getNetIdForResolv()); + } + + /** + * Operates the same as {@code InetAddress.getByName} except that host + * resolution is done on this network. + * + * @param host the hostname to be resolved to an address or {@code null}. + * @return the {@code InetAddress} instance representing the host. + * @throws UnknownHostException + * if the address lookup fails. + */ + public InetAddress getByName(String host) throws UnknownHostException { + return InetAddress.getByNameOnNet(host, getNetIdForResolv()); + } + + /** + * Obtain a Network object for which Private DNS is to be bypassed when attempting + * to use {@link #getAllByName(String)}/{@link #getByName(String)} methods on the given + * instance for hostname resolution. + * + * @hide + */ + @SystemApi + public @NonNull Network getPrivateDnsBypassingCopy() { + return new Network(netId, true); + } + + /** + * Get the unique id of the network. + * + * @hide + */ + @SystemApi + public int getNetId() { + return netId; + } + + /** + * Returns a netid marked with the Private DNS bypass flag. + * + * This flag must be kept in sync with the NETID_USE_LOCAL_NAMESERVERS flag + * in system/netd/include/NetdClient.h. + * + * @hide + */ + public int getNetIdForResolv() { + return mPrivateDnsBypass + ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag. + : netId; + } + + /** + * A {@code SocketFactory} that produces {@code Socket}'s bound to this network. + */ + private class NetworkBoundSocketFactory extends SocketFactory { + private Socket connectToHost(String host, int port, SocketAddress localAddress) + throws IOException { + // Lookup addresses only on this Network. + InetAddress[] hostAddresses = getAllByName(host); + // Try all addresses. + for (int i = 0; i < hostAddresses.length; i++) { + try { + Socket socket = createSocket(); + boolean failed = true; + try { + if (localAddress != null) socket.bind(localAddress); + socket.connect(new InetSocketAddress(hostAddresses[i], port)); + failed = false; + return socket; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } + } catch (IOException e) { + if (i == (hostAddresses.length - 1)) throw e; + } + } + throw new UnknownHostException(host); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException { + return connectToHost(host, port, new InetSocketAddress(localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, + int localPort) throws IOException { + Socket socket = createSocket(); + boolean failed = true; + try { + socket.bind(new InetSocketAddress(localAddress, localPort)); + socket.connect(new InetSocketAddress(address, port)); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } + return socket; + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + Socket socket = createSocket(); + boolean failed = true; + try { + socket.connect(new InetSocketAddress(host, port)); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } + return socket; + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return connectToHost(host, port, null); + } + + @Override + public Socket createSocket() throws IOException { + Socket socket = new Socket(); + boolean failed = true; + try { + bindSocket(socket); + failed = false; + } finally { + if (failed) IoUtils.closeQuietly(socket); + } + return socket; + } + } + + /** + * Returns a {@link SocketFactory} bound to this network. Any {@link Socket} created by + * this factory will have its traffic sent over this {@code Network}. Note that if this + * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the + * past or future will cease to work. + * + * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this + * {@code Network}. + */ + public SocketFactory getSocketFactory() { + if (mNetworkBoundSocketFactory == null) { + synchronized (mLock) { + if (mNetworkBoundSocketFactory == null) { + mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(); + } + } + } + return mNetworkBoundSocketFactory; + } + + private static HttpURLConnectionFactory createUrlConnectionFactory(Dns dnsLookup) { + // Set configuration on the HttpURLConnectionFactory that will be good for all + // connections created by this Network. Configuration that might vary is left + // until openConnection() and passed as arguments. + HttpURLConnectionFactory urlConnectionFactory = new HttpURLConnectionFactory(); + urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup + // A private connection pool just for this Network. + urlConnectionFactory.setNewConnectionPool(httpMaxConnections, + httpKeepAliveDurationMs, TimeUnit.MILLISECONDS); + return urlConnectionFactory; + } + + /** + * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent + * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}. + * + * @return a {@code URLConnection} to the resource referred to by this URL. + * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS. + * @throws IOException if an error occurs while opening the connection. + * @see java.net.URL#openConnection() + */ + public URLConnection openConnection(URL url) throws IOException { + final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull(); + if (cm == null) { + throw new IOException("No ConnectivityManager yet constructed, please construct one"); + } + // TODO: Should this be optimized to avoid fetching the global proxy for every request? + final ProxyInfo proxyInfo = cm.getProxyForNetwork(this); + final java.net.Proxy proxy; + if (proxyInfo != null) { + proxy = proxyInfo.makeProxy(); + } else { + proxy = java.net.Proxy.NO_PROXY; + } + return openConnection(url, proxy); + } + + /** + * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent + * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}. + * + * @param proxy the proxy through which the connection will be established. + * @return a {@code URLConnection} to the resource referred to by this URL. + * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS. + * @throws IllegalArgumentException if the argument proxy is null. + * @throws IOException if an error occurs while opening the connection. + * @see java.net.URL#openConnection() + */ + public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException { + if (proxy == null) throw new IllegalArgumentException("proxy is null"); + // TODO: This creates a connection pool and host resolver for + // every Network object, instead of one for every NetId. This is + // suboptimal, because an app could potentially have more than one + // Network object for the same NetId, causing increased memory footprint + // and performance penalties due to lack of connection reuse (connection + // setup time, congestion window growth time, etc.). + // + // Instead, investigate only having one connection pool and host resolver + // for every NetId, perhaps by using a static HashMap of NetIds to + // connection pools and host resolvers. The tricky part is deciding when + // to remove a map entry; a WeakHashMap shouldn't be used because whether + // a Network is referenced doesn't correlate with whether a new Network + // will be instantiated in the near future with the same NetID. A good + // solution would involve purging empty (or when all connections are timed + // out) ConnectionPools. + final HttpURLConnectionFactory urlConnectionFactory; + synchronized (mLock) { + if (mUrlConnectionFactory == null) { + Dns dnsLookup = hostname -> Arrays.asList(getAllByName(hostname)); + mUrlConnectionFactory = createUrlConnectionFactory(dnsLookup); + } + urlConnectionFactory = mUrlConnectionFactory; + } + SocketFactory socketFactory = getSocketFactory(); + return urlConnectionFactory.openConnection(url, socketFactory, proxy); + } + + /** + * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the + * socket will be sent on this {@code Network}, irrespective of any process-wide network binding + * set by {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be + * connected. + */ + public void bindSocket(DatagramSocket socket) throws IOException { + // Query a property of the underlying socket to ensure that the socket's file descriptor + // exists, is available to bind to a network and is not closed. + socket.getReuseAddress(); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket); + bindSocket(pfd.getFileDescriptor()); + // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the + // dup share the underlying socket in the kernel. The socket is never truly closed until the + // last fd pointing to the socket being closed. So close the dup one after binding the + // socket to control the lifetime of the dup fd. + pfd.close(); + } + + /** + * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket + * will be sent on this {@code Network}, irrespective of any process-wide network binding set by + * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected. + */ + public void bindSocket(Socket socket) throws IOException { + // Query a property of the underlying socket to ensure that the socket's file descriptor + // exists, is available to bind to a network and is not closed. + socket.getReuseAddress(); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + bindSocket(pfd.getFileDescriptor()); + // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the + // dup share the underlying socket in the kernel. The socket is never truly closed until the + // last fd pointing to the socket being closed. So close the dup one after binding the + // socket to control the lifetime of the dup fd. + pfd.close(); + } + + /** + * Binds the specified {@link FileDescriptor} to this {@code Network}. All data traffic on the + * socket represented by this file descriptor will be sent on this {@code Network}, + * irrespective of any process-wide network binding set by + * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected. + */ + public void bindSocket(FileDescriptor fd) throws IOException { + try { + final SocketAddress peer = Os.getpeername(fd); + final InetAddress inetPeer = ((InetSocketAddress) peer).getAddress(); + if (!inetPeer.isAnyLocalAddress()) { + // Apparently, the kernel doesn't update a connected UDP socket's + // routing upon mark changes. + throw new SocketException("Socket is connected"); + } + } catch (ErrnoException e) { + // getpeername() failed. + if (e.errno != OsConstants.ENOTCONN) { + throw e.rethrowAsSocketException(); + } + } catch (ClassCastException e) { + // Wasn't an InetSocketAddress. + throw new SocketException("Only AF_INET/AF_INET6 sockets supported"); + } + + final int err = NetworkUtils.bindSocketToNetwork(fd, netId); + if (err != 0) { + // bindSocketToNetwork returns negative errno. + throw new ErrnoException("Binding socket to network " + netId, -err) + .rethrowAsSocketException(); + } + } + + /** + * Returns a {@link Network} object given a handle returned from {@link #getNetworkHandle}. + * + * @param networkHandle a handle returned from {@link #getNetworkHandle}. + * @return A {@link Network} object derived from {@code networkHandle}. + */ + public static Network fromNetworkHandle(long networkHandle) { + if (networkHandle == 0) { + throw new IllegalArgumentException( + "Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network."); + } + if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC + || networkHandle < 0) { + throw new IllegalArgumentException( + "Value passed to fromNetworkHandle() is not a network handle."); + } + return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE)); + } + + /** + * Returns a handle representing this {@code Network}, for use with the NDK API. + */ + public long getNetworkHandle() { + // The network handle is explicitly not the same as the netId. + // + // The netId is an implementation detail which might be changed in the + // future, or which alone (i.e. in the absence of some additional + // context) might not be sufficient to fully identify a Network. + // + // As such, the intention is to prevent accidental misuse of the API + // that might result if a developer assumed that handles and netIds + // were identical and passing a netId to a call expecting a handle + // "just worked". Such accidental misuse, if widely deployed, might + // prevent future changes to the semantics of the netId field or + // inhibit the expansion of state required for Network objects. + // + // This extra layer of indirection might be seen as paranoia, and might + // never end up being necessary, but the added complexity is trivial. + // At some future date it may be desirable to realign the handle with + // Multiple Provisioning Domains API recommendations, as made by the + // IETF mif working group. + if (netId == 0) { + return 0L; // make this zero condition obvious for debugging + } + return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC; + } + + // implement the Parcelable interface + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(netId); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public Network createFromParcel(Parcel in) { + int netId = in.readInt(); + + return new Network(netId); + } + + public Network[] newArray(int size) { + return new Network[size]; + } + }; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Network)) return false; + Network other = (Network)obj; + return this.netId == other.netId; + } + + @Override + public int hashCode() { + return netId * 11; + } + + @Override + public String toString() { + return Integer.toString(netId); + } + + /** @hide */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(NetworkProto.NET_ID, netId); + proto.end(token); + } +} diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java new file mode 100644 index 0000000000..d22d82d1f4 --- /dev/null +++ b/framework/src/android/net/NetworkAgent.java @@ -0,0 +1,1185 @@ +/* + * Copyright (C) 2014 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; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.data.EpsBearerQosSessionAttributes; +import android.util.Log; + +import com.android.connectivity.aidl.INetworkAgent; +import com.android.connectivity.aidl.INetworkAgentRegistry; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Protocol; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A utility class for handling for communicating between bearer-specific + * code and ConnectivityService. + * + * An agent manages the life cycle of a network. A network starts its + * life cycle when {@link register} is called on NetworkAgent. The network + * is then connecting. When full L3 connectivity has been established, + * the agent should call {@link markConnected} to inform the system that + * this network is ready to use. When the network disconnects its life + * ends and the agent should call {@link unregister}, at which point the + * system will clean up and free resources. + * Any reconnection becomes a new logical network, so after a network + * is disconnected the agent cannot be used any more. Network providers + * should create a new NetworkAgent instance to handle new connections. + * + * A bearer may have more than one NetworkAgent if it can simultaneously + * support separate networks (IMS / Internet / MMS Apns on cellular, or + * perhaps connections with different SSID or P2P for Wi-Fi). + * + * This class supports methods to start and stop sending keepalive packets. + * Keepalive packets are typically sent at periodic intervals over a network + * with NAT when there is no other traffic to avoid the network forcefully + * closing the connection. NetworkAgents that manage technologies that + * have hardware support for keepalive should implement the related + * methods to save battery life. NetworkAgent that cannot get support + * without waking up the CPU should not, as this would be prohibitive in + * terms of battery - these agents should simply not override the related + * methods, which results in the implementation returning + * {@link SocketKeepalive.ERROR_UNSUPPORTED} as appropriate. + * + * Keepalive packets need to be sent at relatively frequent intervals + * (a few seconds to a few minutes). As the contents of keepalive packets + * depend on the current network status, hardware needs to be configured + * to send them and has a limited amount of memory to do so. The HAL + * formalizes this as slots that an implementation can configure to send + * the correct packets. Devices typically have a small number of slots + * per radio technology, and the specific number of slots for each + * technology is specified in configuration files. + * {@see SocketKeepalive} for details. + * + * @hide + */ +@SystemApi +public abstract class NetworkAgent { + /** + * The {@link Network} corresponding to this object. + */ + @Nullable + private volatile Network mNetwork; + + @Nullable + private volatile INetworkAgentRegistry mRegistry; + + private interface RegistryAction { + void execute(@NonNull INetworkAgentRegistry registry) throws RemoteException; + } + + private final Handler mHandler; + private final String LOG_TAG; + private static final boolean DBG = true; + private static final boolean VDBG = false; + private final ArrayList mPreConnectedQueue = new ArrayList<>(); + private volatile long mLastBwRefreshTime = 0; + private static final long BW_REFRESH_MIN_WIN_MS = 500; + private boolean mBandwidthUpdateScheduled = false; + private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false); + @NonNull + private NetworkInfo mNetworkInfo; + @NonNull + private final Object mRegisterLock = new Object(); + + /** + * The ID of the {@link NetworkProvider} that created this object, or + * {@link NetworkProvider#ID_NONE} if unknown. + * @hide + */ + public final int providerId; + + private static final int BASE = Protocol.BASE_NETWORK_AGENT; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform it of + * suspected connectivity problems on its network. The NetworkAgent + * should take steps to verify and correct connectivity. + * @hide + */ + public static final int CMD_SUSPECT_BAD = BASE; + + /** + * Sent by the NetworkAgent (note the EVENT vs CMD prefix) to + * ConnectivityService to pass the current NetworkInfo (connection state). + * Sent when the NetworkInfo changes, mainly due to change of state. + * obj = NetworkInfo + * @hide + */ + public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1; + + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * NetworkCapabilties. + * obj = NetworkCapabilities + * @hide + */ + public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2; + + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * NetworkProperties. + * obj = NetworkProperties + * @hide + */ + public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3; + + /** + * Centralize the place where base network score, and network score scaling, will be + * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE + * @hide + */ + public static final int WIFI_BASE_SCORE = 60; + + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * network score. + * arg1 = network score int + * @hide + */ + public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4; + + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current + * list of underlying networks. + * obj = array of Network objects + * @hide + */ + public static final int EVENT_UNDERLYING_NETWORKS_CHANGED = BASE + 5; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent of the + * networks status - whether we could use the network or could not, due to + * either a bad network configuration (no internet link) or captive portal. + * + * arg1 = either {@code VALID_NETWORK} or {@code INVALID_NETWORK} + * obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String} + * representing URL that Internet probe was redirect to, if it was redirected, + * or mapping to {@code null} otherwise. + * @hide + */ + public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7; + + + /** + * Network validation suceeded. + * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}. + */ + public static final int VALIDATION_STATUS_VALID = 1; + + /** + * Network validation was attempted and failed. This may be received more than once as + * subsequent validation attempts are made. + */ + public static final int VALIDATION_STATUS_NOT_VALID = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "VALIDATION_STATUS_" }, value = { + VALIDATION_STATUS_VALID, + VALIDATION_STATUS_NOT_VALID + }) + public @interface ValidationStatus {} + + // TODO: remove. + /** @hide */ + public static final int VALID_NETWORK = 1; + /** @hide */ + public static final int INVALID_NETWORK = 2; + + /** + * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}. + * @hide + */ + public static final String REDIRECT_URL_KEY = "redirect URL"; + + /** + * Sent by the NetworkAgent to ConnectivityService to indicate this network was + * explicitly selected. This should be sent before the NetworkInfo is marked + * CONNECTED so it can be given special treatment at that time. + * + * obj = boolean indicating whether to use this network even if unvalidated + * @hide + */ + public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent of + * whether the network should in the future be used even if not validated. + * This decision is made by the user, but it is the network transport's + * responsibility to remember it. + * + * arg1 = 1 if true, 0 if false + * @hide + */ + public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent to pull + * the underlying network connection for updated bandwidth information. + * @hide + */ + public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10; + + /** + * Sent by ConnectivityService to the NetworkAgent to request that the specified packet be sent + * periodically on the given interval. + * + * arg1 = the hardware slot number of the keepalive to start + * arg2 = interval in seconds + * obj = KeepalivePacketData object describing the data to be sent + * + * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. + * @hide + */ + public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11; + + /** + * Requests that the specified keepalive packet be stopped. + * + * arg1 = hardware slot number of the keepalive to stop. + * + * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics. + * @hide + */ + public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12; + + /** + * Sent by the NetworkAgent to ConnectivityService to provide status on a socket keepalive + * request. This may either be the reply to a CMD_START_SOCKET_KEEPALIVE, or an asynchronous + * error notification. + * + * This is also sent by KeepaliveTracker to the app's {@link SocketKeepalive}, + * so that the app's {@link SocketKeepalive.Callback} methods can be called. + * + * arg1 = hardware slot number of the keepalive + * arg2 = error code + * @hide + */ + public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13; + + /** + * Sent by ConnectivityService to inform this network transport of signal strength thresholds + * that when crossed should trigger a system wakeup and a NetworkCapabilities update. + * + * obj = int[] describing signal strength thresholds. + * @hide + */ + public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14; + + /** + * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid + * automatically reconnecting to this network (e.g. via autojoin). Happens + * when user selects "No" option on the "Stay connected?" dialog box. + * @hide + */ + public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15; + + /** + * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter. + * + * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the + * remote site will send ACK packets in response to the keepalive packets, the firmware also + * needs to be configured to properly filter the ACKs to prevent the system from waking up. + * This does not happen with UDP, so this message is TCP-specific. + * arg1 = hardware slot number of the keepalive to filter for. + * obj = the keepalive packet to send repeatedly. + * @hide + */ + public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16; + + /** + * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See + * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}. + * arg1 = hardware slot number of the keepalive packet filter to remove. + * @hide + */ + public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17; + + /** + * Sent by ConnectivityService to the NetworkAgent to complete the bidirectional connection. + * obj = INetworkAgentRegistry + */ + private static final int EVENT_AGENT_CONNECTED = BASE + 18; + + /** + * Sent by ConnectivityService to the NetworkAgent to inform the agent that it was disconnected. + */ + private static final int EVENT_AGENT_DISCONNECTED = BASE + 19; + + /** + * Sent by QosCallbackTracker to {@link NetworkAgent} to register a new filter with + * callback. + * + * arg1 = QoS agent callback ID + * obj = {@link QosFilter} + * @hide + */ + public static final int CMD_REGISTER_QOS_CALLBACK = BASE + 20; + + /** + * Sent by QosCallbackTracker to {@link NetworkAgent} to unregister a callback. + * + * arg1 = QoS agent callback ID + * @hide + */ + public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21; + + private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { + // The subtype can be changed with (TODO) setLegacySubtype, but it starts + // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description. + final NetworkInfo ni = new NetworkInfo(config.legacyType, 0, config.legacyTypeName, ""); + ni.setIsAvailable(true); + ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */, + config.getLegacyExtraInfo()); + return ni; + } + + /** + * Create a new network agent. + * @param context a {@link Context} to get system services from. + * @param looper the {@link Looper} on which to invoke the callbacks. + * @param logTag the tag for logs + * @param nc the initial {@link NetworkCapabilities} of this network. Update with + * sendNetworkCapabilities. + * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties. + * @param score the initial score of this network. Update with sendNetworkScore. + * @param config an immutable {@link NetworkAgentConfig} for this agent. + * @param provider the {@link NetworkProvider} managing this agent. + */ + public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, + @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { + this(looper, context, logTag, nc, lp, score, config, + provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(), + getLegacyNetworkInfo(config)); + } + + private static class InitialConfiguration { + public final Context context; + public final NetworkCapabilities capabilities; + public final LinkProperties properties; + public final int score; + public final NetworkAgentConfig config; + public final NetworkInfo info; + InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities, + @NonNull LinkProperties properties, int score, @NonNull NetworkAgentConfig config, + @NonNull NetworkInfo info) { + this.context = context; + this.capabilities = capabilities; + this.properties = properties; + this.score = score; + this.config = config; + this.info = info; + } + } + private volatile InitialConfiguration mInitialConfiguration; + + private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag, + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, + @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) { + mHandler = new NetworkAgentHandler(looper); + LOG_TAG = logTag; + mNetworkInfo = new NetworkInfo(ni); + this.providerId = providerId; + if (ni == null || nc == null || lp == null) { + throw new IllegalArgumentException(); + } + + mInitialConfiguration = new InitialConfiguration(context, + new NetworkCapabilities(nc, /* parcelLocationSensitiveFields */ true), + new LinkProperties(lp), score, config, ni); + } + + private class NetworkAgentHandler extends Handler { + NetworkAgentHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AGENT_CONNECTED: { + if (mRegistry != null) { + log("Received new connection while already connected!"); + } else { + if (VDBG) log("NetworkAgent fully connected"); + synchronized (mPreConnectedQueue) { + final INetworkAgentRegistry registry = (INetworkAgentRegistry) msg.obj; + mRegistry = registry; + for (RegistryAction a : mPreConnectedQueue) { + try { + a.execute(registry); + } catch (RemoteException e) { + Log.wtf(LOG_TAG, "Communication error with registry", e); + // Fall through + } + } + mPreConnectedQueue.clear(); + } + } + break; + } + case EVENT_AGENT_DISCONNECTED: { + if (DBG) log("NetworkAgent channel lost"); + // let the client know CS is done with us. + onNetworkUnwanted(); + synchronized (mPreConnectedQueue) { + mRegistry = null; + } + break; + } + case CMD_SUSPECT_BAD: { + log("Unhandled Message " + msg); + break; + } + case CMD_REQUEST_BANDWIDTH_UPDATE: { + long currentTimeMs = System.currentTimeMillis(); + if (VDBG) { + log("CMD_REQUEST_BANDWIDTH_UPDATE request received."); + } + if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) { + mBandwidthUpdateScheduled = false; + if (!mBandwidthUpdatePending.getAndSet(true)) { + onBandwidthUpdateRequested(); + } + } else { + // deliver the request at a later time rather than discard it completely. + if (!mBandwidthUpdateScheduled) { + long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS + - currentTimeMs + 1; + mBandwidthUpdateScheduled = sendEmptyMessageDelayed( + CMD_REQUEST_BANDWIDTH_UPDATE, waitTime); + } + } + break; + } + case CMD_REPORT_NETWORK_STATUS: { + String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY); + if (VDBG) { + log("CMD_REPORT_NETWORK_STATUS(" + + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + + redirectUrl); + } + Uri uri = null; + try { + if (null != redirectUrl) { + uri = Uri.parse(redirectUrl); + } + } catch (Exception e) { + Log.wtf(LOG_TAG, "Surprising URI : " + redirectUrl, e); + } + onValidationStatus(msg.arg1 /* status */, uri); + break; + } + case CMD_SAVE_ACCEPT_UNVALIDATED: { + onSaveAcceptUnvalidated(msg.arg1 != 0); + break; + } + case CMD_START_SOCKET_KEEPALIVE: { + onStartSocketKeepalive(msg.arg1 /* slot */, + Duration.ofSeconds(msg.arg2) /* interval */, + (KeepalivePacketData) msg.obj /* packet */); + break; + } + case CMD_STOP_SOCKET_KEEPALIVE: { + onStopSocketKeepalive(msg.arg1 /* slot */); + break; + } + + case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: { + onSignalStrengthThresholdsUpdated((int[]) msg.obj); + break; + } + case CMD_PREVENT_AUTOMATIC_RECONNECT: { + onAutomaticReconnectDisabled(); + break; + } + case CMD_ADD_KEEPALIVE_PACKET_FILTER: { + onAddKeepalivePacketFilter(msg.arg1 /* slot */, + (KeepalivePacketData) msg.obj /* packet */); + break; + } + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { + onRemoveKeepalivePacketFilter(msg.arg1 /* slot */); + break; + } + case CMD_REGISTER_QOS_CALLBACK: { + onQosCallbackRegistered( + msg.arg1 /* QoS callback id */, + (QosFilter) msg.obj /* QoS filter */); + break; + } + case CMD_UNREGISTER_QOS_CALLBACK: { + onQosCallbackUnregistered( + msg.arg1 /* QoS callback id */); + break; + } + } + } + } + + /** + * Register this network agent with ConnectivityService. + * + * This method can only be called once per network agent. + * + * @return the Network associated with this network agent (which can also be obtained later + * by calling getNetwork() on this agent). + * @throws IllegalStateException thrown by the system server if this network agent is + * already registered. + */ + @NonNull + public Network register() { + if (VDBG) log("Registering NetworkAgent"); + synchronized (mRegisterLock) { + if (mNetwork != null) { + throw new IllegalStateException("Agent already registered"); + } + final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context + .getSystemService(Context.CONNECTIVITY_SERVICE); + mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), + new NetworkInfo(mInitialConfiguration.info), + mInitialConfiguration.properties, mInitialConfiguration.capabilities, + mInitialConfiguration.score, mInitialConfiguration.config, providerId); + mInitialConfiguration = null; // All this memory can now be GC'd + } + return mNetwork; + } + + private static class NetworkAgentBinder extends INetworkAgent.Stub { + private static final String LOG_TAG = NetworkAgentBinder.class.getSimpleName(); + + private final Handler mHandler; + + private NetworkAgentBinder(Handler handler) { + mHandler = handler; + } + + @Override + public void onRegistered(@NonNull INetworkAgentRegistry registry) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_CONNECTED, registry)); + } + + @Override + public void onDisconnected() { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_AGENT_DISCONNECTED)); + } + + @Override + public void onBandwidthUpdateRequested() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_REQUEST_BANDWIDTH_UPDATE)); + } + + @Override + public void onValidationStatusChanged( + int validationStatus, @Nullable String captivePortalUrl) { + // TODO: consider using a parcelable as argument when the interface is structured + Bundle redirectUrlBundle = new Bundle(); + redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, captivePortalUrl); + mHandler.sendMessage(mHandler.obtainMessage(CMD_REPORT_NETWORK_STATUS, + validationStatus, 0, redirectUrlBundle)); + } + + @Override + public void onSaveAcceptUnvalidated(boolean acceptUnvalidated) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_SAVE_ACCEPT_UNVALIDATED, + acceptUnvalidated ? 1 : 0, 0)); + } + + @Override + public void onStartNattSocketKeepalive(int slot, int intervalDurationMs, + @NonNull NattKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, + slot, intervalDurationMs, packetData)); + } + + @Override + public void onStartTcpSocketKeepalive(int slot, int intervalDurationMs, + @NonNull TcpKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, + slot, intervalDurationMs, packetData)); + } + + @Override + public void onStopSocketKeepalive(int slot) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0)); + } + + @Override + public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) { + mHandler.sendMessage(mHandler.obtainMessage( + CMD_SET_SIGNAL_STRENGTH_THRESHOLDS, thresholds)); + } + + @Override + public void onPreventAutomaticReconnect() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)); + } + + @Override + public void onAddNattKeepalivePacketFilter(int slot, + @NonNull NattKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, + slot, 0, packetData)); + } + + @Override + public void onAddTcpKeepalivePacketFilter(int slot, + @NonNull TcpKeepalivePacketData packetData) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, + slot, 0, packetData)); + } + + @Override + public void onRemoveKeepalivePacketFilter(int slot) { + mHandler.sendMessage(mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, + slot, 0)); + } + + @Override + public void onQosFilterCallbackRegistered(final int qosCallbackId, + final QosFilterParcelable qosFilterParcelable) { + if (qosFilterParcelable.getQosFilter() != null) { + mHandler.sendMessage( + mHandler.obtainMessage(CMD_REGISTER_QOS_CALLBACK, qosCallbackId, 0, + qosFilterParcelable.getQosFilter())); + return; + } + + Log.wtf(LOG_TAG, "onQosFilterCallbackRegistered: qos filter is null."); + } + + @Override + public void onQosCallbackUnregistered(final int qosCallbackId) { + mHandler.sendMessage(mHandler.obtainMessage( + CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null)); + } + } + + /** + * Register this network agent with a testing harness. + * + * The returned Messenger sends messages to the Handler. This allows a test to send + * this object {@code CMD_*} messages as if they came from ConnectivityService, which + * is useful for testing the behavior. + * + * @hide + */ + public INetworkAgent registerForTest(final Network network) { + log("Registering NetworkAgent for test"); + synchronized (mRegisterLock) { + mNetwork = network; + mInitialConfiguration = null; + } + return new NetworkAgentBinder(mHandler); + } + + /** + * Waits for the handler to be idle. + * This is useful for testing, and has smaller scope than an accessor to mHandler. + * TODO : move the implementation in common library with the tests + * @hide + */ + @VisibleForTesting + public boolean waitForIdle(final long timeoutMs) { + final ConditionVariable cv = new ConditionVariable(false); + mHandler.post(cv::open); + return cv.block(timeoutMs); + } + + /** + * @return The Network associated with this agent, or null if it's not registered yet. + */ + @Nullable + public Network getNetwork() { + return mNetwork; + } + + private void queueOrSendMessage(@NonNull RegistryAction action) { + synchronized (mPreConnectedQueue) { + if (mRegistry != null) { + try { + action.execute(mRegistry); + } catch (RemoteException e) { + Log.wtf(LOG_TAG, "Error executing registry action", e); + // Fall through: the channel is asynchronous and does not report errors back + } + } else { + mPreConnectedQueue.add(action); + } + } + } + + /** + * Must be called by the agent when the network's {@link LinkProperties} change. + * @param linkProperties the new LinkProperties. + */ + public final void sendLinkProperties(@NonNull LinkProperties linkProperties) { + Objects.requireNonNull(linkProperties); + final LinkProperties lp = new LinkProperties(linkProperties); + queueOrSendMessage(reg -> reg.sendLinkProperties(lp)); + } + + /** + * Must be called by the agent when the network's underlying networks change. + * + *

{@code networks} is one of the following: + *

    + *
  • a non-empty array: an array of one or more {@link Network}s, in + * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular) + * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear + * first in the array.
  • + *
  • an empty array: a zero-element array, meaning that the VPN has no + * underlying network connection, and thus, app traffic will not be sent or received.
  • + *
  • null: (default) signifies that the VPN uses whatever is the system's + * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket} + * APIs mentioned above to send traffic over specific channels.
  • + *
+ * + * @param underlyingNetworks the new list of underlying networks. + * @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])} + */ + public final void setUnderlyingNetworks(@Nullable List underlyingNetworks) { + final ArrayList underlyingArray = (underlyingNetworks != null) + ? new ArrayList<>(underlyingNetworks) : null; + queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray)); + } + + /** + * Inform ConnectivityService that this agent has now connected. + * Call {@link #unregister} to disconnect. + */ + public void markConnected() { + mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null /* reason */, + mNetworkInfo.getExtraInfo()); + queueOrSendNetworkInfo(mNetworkInfo); + } + + /** + * Unregister this network agent. + * + * This signals the network has disconnected and ends its lifecycle. After this is called, + * the network is torn down and this agent can no longer be used. + */ + public void unregister() { + // When unregistering an agent nobody should use the extrainfo (or reason) any more. + mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null /* reason */, + null /* extraInfo */); + queueOrSendNetworkInfo(mNetworkInfo); + } + + /** + * Change the legacy subtype of this network agent. + * + * This is only for backward compatibility and should not be used by non-legacy network agents, + * or agents that did not use to set a subtype. As such, only TYPE_MOBILE type agents can use + * this and others will be thrown an exception if they try. + * + * @deprecated this is for backward compatibility only. + * @param legacySubtype the legacy subtype. + * @hide + */ + @Deprecated + public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) { + mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName); + queueOrSendNetworkInfo(mNetworkInfo); + } + + /** + * Set the ExtraInfo of this network agent. + * + * This sets the ExtraInfo field inside the NetworkInfo returned by legacy public API and the + * broadcasts about the corresponding Network. + * This is only for backward compatibility and should not be used by non-legacy network agents, + * who will be thrown an exception if they try. The extra info should only be : + *
    + *
  • For cellular agents, the APN name.
  • + *
  • For ethernet agents, the interface name.
  • + *
+ * + * @deprecated this is for backward compatibility only. + * @param extraInfo the ExtraInfo. + * @hide + */ + @Deprecated + public void setLegacyExtraInfo(@Nullable final String extraInfo) { + mNetworkInfo.setExtraInfo(extraInfo); + queueOrSendNetworkInfo(mNetworkInfo); + } + + /** + * Must be called by the agent when it has a new NetworkInfo object. + * @hide TODO: expose something better. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public final void sendNetworkInfo(NetworkInfo networkInfo) { + queueOrSendNetworkInfo(new NetworkInfo(networkInfo)); + } + + private void queueOrSendNetworkInfo(NetworkInfo networkInfo) { + queueOrSendMessage(reg -> reg.sendNetworkInfo(networkInfo)); + } + + /** + * Must be called by the agent when the network's {@link NetworkCapabilities} change. + * @param networkCapabilities the new NetworkCapabilities. + */ + public final void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { + Objects.requireNonNull(networkCapabilities); + mBandwidthUpdatePending.set(false); + mLastBwRefreshTime = System.currentTimeMillis(); + final NetworkCapabilities nc = + new NetworkCapabilities(networkCapabilities, + /* parcelLocationSensitiveFields */ true); + queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc)); + } + + /** + * Must be called by the agent to update the score of this network. + * + * @param score the new score, between 0 and 99. + */ + public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) { + if (score < 0) { + throw new IllegalArgumentException("Score must be >= 0"); + } + queueOrSendMessage(reg -> reg.sendScore(score)); + } + + /** + * Must be called by the agent to indicate this network was manually selected by the user. + * This should be called before the NetworkInfo is marked CONNECTED so that this + * Network can be given special treatment at that time. If {@code acceptUnvalidated} is + * {@code true}, then the system will switch to this network. If it is {@code false} and the + * network cannot be validated, the system will ask the user whether to switch to this network. + * If the user confirms and selects "don't ask again", then the system will call + * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever + * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement + * {@link #saveAcceptUnvalidated} to respect the user's choice. + * @hide should move to NetworkAgentConfig. + */ + public void explicitlySelected(boolean acceptUnvalidated) { + explicitlySelected(true /* explicitlySelected */, acceptUnvalidated); + } + + /** + * Must be called by the agent to indicate whether the network was manually selected by the + * user. This should be called before the network becomes connected, so it can be given + * special treatment when it does. + * + * If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true}, + * then the system will switch to this network. If {@code explicitlySelected} is {@code true} + * and {@code acceptUnvalidated} is {@code false}, and the network cannot be validated, the + * system will ask the user whether to switch to this network. If the user confirms and selects + * "don't ask again", then the system will call {@link #saveAcceptUnvalidated} to persist the + * user's choice. Thus, if the transport ever calls this method with {@code explicitlySelected} + * set to {@code true} and {@code acceptUnvalidated} set to {@code false}, it must also + * implement {@link #saveAcceptUnvalidated} to respect the user's choice. + * + * If {@code explicitlySelected} is {@code false} and {@code acceptUnvalidated} is + * {@code true}, the system will interpret this as the user having accepted partial connectivity + * on this network. Thus, the system will switch to the network and consider it validated even + * if it only provides partial connectivity, but the network is not otherwise treated specially. + * @hide should move to NetworkAgentConfig. + */ + public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) { + queueOrSendMessage(reg -> reg.sendExplicitlySelected( + explicitlySelected, acceptUnvalidated)); + } + + /** + * Called when ConnectivityService has indicated they no longer want this network. + * The parent factory should (previously) have received indication of the change + * as well, either canceling NetworkRequests or altering their score such that this + * network won't be immediately requested again. + */ + public void onNetworkUnwanted() { + unwanted(); + } + /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */ + protected void unwanted() { + } + + /** + * Called when ConnectivityService request a bandwidth update. The parent factory + * shall try to overwrite this method and produce a bandwidth update if capable. + * @hide + */ + public void onBandwidthUpdateRequested() { + pollLceData(); + } + /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */ + protected void pollLceData() { + } + + /** + * Called when the system determines the usefulness of this network. + * + * The system attempts to validate Internet connectivity on networks that provide the + * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability. + * + * Currently there are two possible values: + * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated, + * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated. + * + * This is guaranteed to be called again when the network status changes, but the system + * may also call this multiple times even if the status does not change. + * + * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}. + * @param redirectUri If Internet connectivity is being redirected (e.g., on a captive portal), + * this is the destination the probes are being redirected to, otherwise {@code null}. + */ + public void onValidationStatus(@ValidationStatus int status, @Nullable Uri redirectUri) { + networkStatus(status, null == redirectUri ? "" : redirectUri.toString()); + } + /** @hide TODO delete once subclasses have moved to onValidationStatus */ + protected void networkStatus(int status, String redirectUrl) { + } + + + /** + * Called when the user asks to remember the choice to use this network even if unvalidated. + * The transport is responsible for remembering the choice, and the next time the user connects + * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}. + * This method will only be called if {@link #explicitlySelected} was called with + * {@code acceptUnvalidated} set to {@code false}. + * @param accept whether the user wants to use the network even if unvalidated. + */ + public void onSaveAcceptUnvalidated(boolean accept) { + saveAcceptUnvalidated(accept); + } + /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */ + protected void saveAcceptUnvalidated(boolean accept) { + } + + /** + * Requests that the network hardware send the specified packet at the specified interval. + * + * @param slot the hardware slot on which to start the keepalive. + * @param interval the interval between packets, between 10 and 3600. Note that this API + * does not support sub-second precision and will round off the request. + * @param packet the packet to send. + */ + // seconds is from SocketKeepalive.MIN_INTERVAL_SEC to MAX_INTERVAL_SEC, but these should + // not be exposed as constants because they may change in the future (API guideline 4.8) + // and should have getters if exposed at all. Getters can't be used in the annotation, + // so the values unfortunately need to be copied. + public void onStartSocketKeepalive(int slot, @NonNull Duration interval, + @NonNull KeepalivePacketData packet) { + final long intervalSeconds = interval.getSeconds(); + if (intervalSeconds < SocketKeepalive.MIN_INTERVAL_SEC + || intervalSeconds > SocketKeepalive.MAX_INTERVAL_SEC) { + throw new IllegalArgumentException("Interval needs to be comprised between " + + SocketKeepalive.MIN_INTERVAL_SEC + " and " + SocketKeepalive.MAX_INTERVAL_SEC + + " but was " + intervalSeconds); + } + final Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, + (int) intervalSeconds, packet); + startSocketKeepalive(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */ + protected void startSocketKeepalive(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); + } + + /** + * Requests that the network hardware stop a previously-started keepalive. + * + * @param slot the hardware slot on which to stop the keepalive. + */ + public void onStopSocketKeepalive(int slot) { + Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null); + stopSocketKeepalive(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */ + protected void stopSocketKeepalive(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED); + } + + /** + * Must be called by the agent when a socket keepalive event occurs. + * + * @param slot the hardware slot on which the event occurred. + * @param event the event that occurred, as one of the SocketKeepalive.ERROR_* + * or SocketKeepalive.SUCCESS constants. + */ + public final void sendSocketKeepaliveEvent(int slot, + @SocketKeepalive.KeepaliveEvent int event) { + queueOrSendMessage(reg -> reg.sendSocketKeepaliveEvent(slot, event)); + } + /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */ + public void onSocketKeepaliveEvent(int slot, int reason) { + sendSocketKeepaliveEvent(slot, reason); + } + + /** + * Called by ConnectivityService to add specific packet filter to network hardware to block + * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support + * this feature must override this method. + * + * @param slot the hardware slot on which the keepalive should be sent. + * @param packet the packet that is being sent. + */ + public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) { + Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet); + addKeepalivePacketFilter(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */ + protected void addKeepalivePacketFilter(Message msg) { + } + + /** + * Called by ConnectivityService to remove a packet filter installed with + * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature + * must override this method. + * + * @param slot the hardware slot on which the keepalive is being sent. + */ + public void onRemoveKeepalivePacketFilter(int slot) { + Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null); + removeKeepalivePacketFilter(msg); + msg.recycle(); + } + /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */ + protected void removeKeepalivePacketFilter(Message msg) { + } + + /** + * Called by ConnectivityService to inform this network agent of signal strength thresholds + * that when crossed should trigger a system wakeup and a NetworkCapabilities update. + * + * When the system updates the list of thresholds that should wake up the CPU for a + * given agent it will call this method on the agent. The agent that implement this + * should implement it in hardware so as to ensure the CPU will be woken up on breach. + * Agents are expected to react to a breach by sending an updated NetworkCapabilities + * object with the appropriate signal strength to sendNetworkCapabilities. + * + * The specific units are bearer-dependent. See details on the units and requests in + * {@link NetworkCapabilities.Builder#setSignalStrength}. + * + * @param thresholds the array of thresholds that should trigger wakeups. + */ + public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) { + setSignalStrengthThresholds(thresholds); + } + /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */ + protected void setSignalStrengthThresholds(int[] thresholds) { + } + + /** + * Called when the user asks to not stay connected to this network because it was found to not + * provide Internet access. Usually followed by call to {@code unwanted}. The transport is + * responsible for making sure the device does not automatically reconnect to the same network + * after the {@code unwanted} call. + */ + public void onAutomaticReconnectDisabled() { + preventAutomaticReconnect(); + } + /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */ + protected void preventAutomaticReconnect() { + } + + /** + * Called when a qos callback is registered with a filter. + * @param qosCallbackId the id for the callback registered + * @param filter the filter being registered + */ + public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) { + } + + /** + * Called when a qos callback is registered with a filter. + *

+ * Any QoS events that are sent with the same callback id after this method is called + * are a no-op. + * + * @param qosCallbackId the id for the callback being unregistered + */ + public void onQosCallbackUnregistered(final int qosCallbackId) { + } + + + /** + * Sends the attributes of Eps Bearer Qos Session back to the Application + * + * @param qosCallbackId the callback id that the session belongs to + * @param sessionId the unique session id across all Eps Bearer Qos Sessions + * @param attributes the attributes of the Eps Qos Session + */ + public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId, + @NonNull final EpsBearerQosSessionAttributes attributes) { + Objects.requireNonNull(attributes, "The attributes must be non-null"); + queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), + attributes)); + } + + /** + * Sends event that the Eps Qos Session was lost. + * + * @param qosCallbackId the callback id that the session belongs to + * @param sessionId the unique session id across all Eps Bearer Qos Sessions + */ + public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) { + queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER))); + } + + /** + * Sends the exception type back to the application. + * + * The NetworkAgent should not send anymore messages with this id. + * + * @param qosCallbackId the callback id this exception belongs to + * @param exceptionType the type of exception + */ + public final void sendQosCallbackError(final int qosCallbackId, + @QosCallbackException.ExceptionType final int exceptionType) { + queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType)); + } + + + /** @hide */ + protected void log(final String s) { + Log.d(LOG_TAG, "NetworkAgent: " + s); + } +} diff --git a/framework/src/android/net/NetworkAgentConfig.aidl b/framework/src/android/net/NetworkAgentConfig.aidl new file mode 100644 index 0000000000..cb70bdd312 --- /dev/null +++ b/framework/src/android/net/NetworkAgentConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 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; + +parcelable NetworkAgentConfig; diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java new file mode 100644 index 0000000000..664c2650ff --- /dev/null +++ b/framework/src/android/net/NetworkAgentConfig.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2014 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; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Allows a network transport to provide the system with policy and configuration information about + * a particular network when registering a {@link NetworkAgent}. This information cannot change once the agent is registered. + * + * @hide + */ +@SystemApi +public final class NetworkAgentConfig implements Parcelable { + + /** + * If the {@link Network} is a VPN, whether apps are allowed to bypass the + * VPN. This is set by a {@link VpnService} and used by + * {@link ConnectivityManager} when creating a VPN. + * + * @hide + */ + public boolean allowBypass; + + /** + * Set if the network was manually/explicitly connected to by the user either from settings + * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi + * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to + * connect to a particular access point is also explicit, though this may change in the future + * as we want apps to use the multinetwork apis. + * + * @hide + */ + public boolean explicitlySelected; + + /** + * @return whether this network was explicitly selected by the user. + */ + public boolean isExplicitlySelected() { + return explicitlySelected; + } + + /** + * Set if the user desires to use this network even if it is unvalidated. This field has meaning + * only if {@link explicitlySelected} is true. If it is, this field must also be set to the + * appropriate value based on previous user choice. + * + * TODO : rename this field to match its accessor + * @hide + */ + public boolean acceptUnvalidated; + + /** + * @return whether the system should accept this network even if it doesn't validate. + */ + public boolean isUnvalidatedConnectivityAcceptable() { + return acceptUnvalidated; + } + + /** + * Whether the user explicitly set that this network should be validated even if presence of + * only partial internet connectivity. + * + * TODO : rename this field to match its accessor + * @hide + */ + public boolean acceptPartialConnectivity; + + /** + * @return whether the system should validate this network even if it only offers partial + * Internet connectivity. + */ + public boolean isPartialConnectivityAcceptable() { + return acceptPartialConnectivity; + } + + /** + * Set to avoid surfacing the "Sign in to network" notification. + * if carrier receivers/apps are registered to handle the carrier-specific provisioning + * procedure, a carrier specific provisioning notification will be placed. + * only one notification should be displayed. This field is set based on + * which notification should be used for provisioning. + * + * @hide + */ + public boolean provisioningNotificationDisabled; + + /** + * + * @return whether the sign in to network notification is enabled by this configuration. + * @hide + */ + public boolean isProvisioningNotificationEnabled() { + return !provisioningNotificationDisabled; + } + + /** + * For mobile networks, this is the subscriber ID (such as IMSI). + * + * @hide + */ + public String subscriberId; + + /** + * @return the subscriber ID, or null if none. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @Nullable + public String getSubscriberId() { + return subscriberId; + } + + /** + * Set to skip 464xlat. This means the device will treat the network as IPv6-only and + * will not attempt to detect a NAT64 via RFC 7050 DNS lookups. + * + * @hide + */ + public boolean skip464xlat; + + /** + * @return whether NAT64 prefix detection is enabled. + * @hide + */ + public boolean isNat64DetectionEnabled() { + return !skip464xlat; + } + + /** + * The legacy type of this network agent, or TYPE_NONE if unset. + * @hide + */ + public int legacyType = ConnectivityManager.TYPE_NONE; + + /** + * @return the legacy type + */ + @ConnectivityManager.LegacyNetworkType + public int getLegacyType() { + return legacyType; + } + + /** + * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network. + * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode. + * + * This is not parceled, because it would not make sense. + * + * @hide + */ + public transient boolean hasShownBroken; + + /** + * The name of the legacy network type. It's a free-form string used in logging. + * @hide + */ + @NonNull + public String legacyTypeName = ""; + + /** + * @return the name of the legacy network type. It's a free-form string used in logging. + */ + @NonNull + public String getLegacyTypeName() { + return legacyTypeName; + } + + /** + * The legacy extra info of the agent. The extra info should only be : + *

    + *
  • For cellular agents, the APN name.
  • + *
  • For ethernet agents, the interface name.
  • + *
+ * @hide + */ + @NonNull + private String mLegacyExtraInfo = ""; + + /** + * The legacy extra info of the agent. + * @hide + */ + @NonNull + public String getLegacyExtraInfo() { + return mLegacyExtraInfo; + } + + /** @hide */ + public NetworkAgentConfig() { + } + + /** @hide */ + public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) { + if (nac != null) { + allowBypass = nac.allowBypass; + explicitlySelected = nac.explicitlySelected; + acceptUnvalidated = nac.acceptUnvalidated; + acceptPartialConnectivity = nac.acceptPartialConnectivity; + subscriberId = nac.subscriberId; + provisioningNotificationDisabled = nac.provisioningNotificationDisabled; + skip464xlat = nac.skip464xlat; + legacyType = nac.legacyType; + legacyTypeName = nac.legacyTypeName; + mLegacyExtraInfo = nac.mLegacyExtraInfo; + } + } + + /** + * Builder class to facilitate constructing {@link NetworkAgentConfig} objects. + */ + public static final class Builder { + private final NetworkAgentConfig mConfig = new NetworkAgentConfig(); + + /** + * Sets whether the network was explicitly selected by the user. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setExplicitlySelected(final boolean explicitlySelected) { + mConfig.explicitlySelected = explicitlySelected; + return this; + } + + /** + * Sets whether the system should validate this network even if it is found not to offer + * Internet connectivity. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setUnvalidatedConnectivityAcceptable( + final boolean unvalidatedConnectivityAcceptable) { + mConfig.acceptUnvalidated = unvalidatedConnectivityAcceptable; + return this; + } + + /** + * Sets whether the system should validate this network even if it is found to only offer + * partial Internet connectivity. + * + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setPartialConnectivityAcceptable( + final boolean partialConnectivityAcceptable) { + mConfig.acceptPartialConnectivity = partialConnectivityAcceptable; + return this; + } + + /** + * Sets the subscriber ID for this network. + * + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + public Builder setSubscriberId(@Nullable String subscriberId) { + mConfig.subscriberId = subscriberId; + return this; + } + + /** + * Disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to save power + * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64. + * + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + public Builder disableNat64Detection() { + mConfig.skip464xlat = true; + return this; + } + + /** + * Disables the "Sign in to network" notification. Used if the network transport will + * perform its own carrier-specific provisioning procedure. + * + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + public Builder disableProvisioningNotification() { + mConfig.provisioningNotificationDisabled = true; + return this; + } + + /** + * Sets the legacy type for this network. + * + * @param legacyType the type + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setLegacyType(int legacyType) { + mConfig.legacyType = legacyType; + return this; + } + + /** + * Sets the name of the legacy type of the agent. It's a free-form string used in logging. + * @param legacyTypeName the name + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setLegacyTypeName(@NonNull String legacyTypeName) { + mConfig.legacyTypeName = legacyTypeName; + return this; + } + + /** + * Sets the legacy extra info of the agent. + * @param legacyExtraInfo the legacy extra info. + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) { + mConfig.mLegacyExtraInfo = legacyExtraInfo; + return this; + } + + /** + * Returns the constructed {@link NetworkAgentConfig} object. + */ + @NonNull + public NetworkAgentConfig build() { + return mConfig; + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final NetworkAgentConfig that = (NetworkAgentConfig) o; + return allowBypass == that.allowBypass + && explicitlySelected == that.explicitlySelected + && acceptUnvalidated == that.acceptUnvalidated + && acceptPartialConnectivity == that.acceptPartialConnectivity + && provisioningNotificationDisabled == that.provisioningNotificationDisabled + && skip464xlat == that.skip464xlat + && legacyType == that.legacyType + && Objects.equals(subscriberId, that.subscriberId) + && Objects.equals(legacyTypeName, that.legacyTypeName) + && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo); + } + + @Override + public int hashCode() { + return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated, + acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId, + skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo); + } + + @Override + public String toString() { + return "NetworkAgentConfig {" + + " allowBypass = " + allowBypass + + ", explicitlySelected = " + explicitlySelected + + ", acceptUnvalidated = " + acceptUnvalidated + + ", acceptPartialConnectivity = " + acceptPartialConnectivity + + ", provisioningNotificationDisabled = " + provisioningNotificationDisabled + + ", subscriberId = '" + subscriberId + '\'' + + ", skip464xlat = " + skip464xlat + + ", legacyType = " + legacyType + + ", hasShownBroken = " + hasShownBroken + + ", legacyTypeName = '" + legacyTypeName + '\'' + + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\'' + + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(allowBypass ? 1 : 0); + out.writeInt(explicitlySelected ? 1 : 0); + out.writeInt(acceptUnvalidated ? 1 : 0); + out.writeInt(acceptPartialConnectivity ? 1 : 0); + out.writeString(subscriberId); + out.writeInt(provisioningNotificationDisabled ? 1 : 0); + out.writeInt(skip464xlat ? 1 : 0); + out.writeInt(legacyType); + out.writeString(legacyTypeName); + out.writeString(mLegacyExtraInfo); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + @Override + public NetworkAgentConfig createFromParcel(Parcel in) { + NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig(); + networkAgentConfig.allowBypass = in.readInt() != 0; + networkAgentConfig.explicitlySelected = in.readInt() != 0; + networkAgentConfig.acceptUnvalidated = in.readInt() != 0; + networkAgentConfig.acceptPartialConnectivity = in.readInt() != 0; + networkAgentConfig.subscriberId = in.readString(); + networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0; + networkAgentConfig.skip464xlat = in.readInt() != 0; + networkAgentConfig.legacyType = in.readInt(); + networkAgentConfig.legacyTypeName = in.readString(); + networkAgentConfig.mLegacyExtraInfo = in.readString(); + return networkAgentConfig; + } + + @Override + public NetworkAgentConfig[] newArray(int size) { + return new NetworkAgentConfig[size]; + } + }; +} diff --git a/framework/src/android/net/NetworkCapabilities.aidl b/framework/src/android/net/NetworkCapabilities.aidl new file mode 100644 index 0000000000..01d328605d --- /dev/null +++ b/framework/src/android/net/NetworkCapabilities.aidl @@ -0,0 +1,21 @@ +/* +** +** Copyright (C) 2014 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; + +@JavaOnlyStableParcelable parcelable NetworkCapabilities; + diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java new file mode 100644 index 0000000000..3843b9ab93 --- /dev/null +++ b/framework/src/android/net/NetworkCapabilities.java @@ -0,0 +1,2517 @@ +/* + * Copyright (C) 2014 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; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.net.ConnectivityManager.NetworkCallback; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.BitUtils; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; + +/** + * Representation of the capabilities of an active network. Instances are + * typically obtained through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} + * or {@link ConnectivityManager#getNetworkCapabilities(Network)}. + *

+ * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of + * network selection. Rather than indicate a need for Wi-Fi because an + * application needs high bandwidth and risk obsolescence when a new, fast + * network appears (like LTE), the application should specify it needs high + * bandwidth. Similarly if an application needs an unmetered network for a bulk + * transfer it can specify that rather than assuming all cellular based + * connections are metered and all Wi-Fi based connections are not. + */ +public final class NetworkCapabilities implements Parcelable { + private static final String TAG = "NetworkCapabilities"; + + // Set to true when private DNS is broken. + private boolean mPrivateDnsBroken; + + /** + * Uid of the app making the request. + */ + private int mRequestorUid; + + /** + * Package name of the app making the request. + */ + private String mRequestorPackageName; + + /** + * Indicates whether parceling should preserve fields that are set based on permissions of + * the process receiving the {@link NetworkCapabilities}. + */ + private final boolean mParcelLocationSensitiveFields; + + public NetworkCapabilities() { + mParcelLocationSensitiveFields = false; + clearAll(); + mNetworkCapabilities = DEFAULT_CAPABILITIES; + } + + public NetworkCapabilities(NetworkCapabilities nc) { + this(nc, false /* parcelLocationSensitiveFields */); + } + + /** + * Make a copy of NetworkCapabilities. + * + * @param nc Original NetworkCapabilities + * @param parcelLocationSensitiveFields Whether to parcel location sensitive data or not. + * @hide + */ + @SystemApi + public NetworkCapabilities( + @Nullable NetworkCapabilities nc, boolean parcelLocationSensitiveFields) { + mParcelLocationSensitiveFields = parcelLocationSensitiveFields; + if (nc != null) { + set(nc); + } + } + + /** + * Completely clears the contents of this object, removing even the capabilities that are set + * by default when the object is constructed. + * @hide + */ + public void clearAll() { + // Ensures that the internal copies maintained by the connectivity stack does not set + // this bit. + if (mParcelLocationSensitiveFields) { + throw new UnsupportedOperationException( + "Cannot clear NetworkCapabilities when parcelLocationSensitiveFields is set"); + } + mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0; + mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + mNetworkSpecifier = null; + mTransportInfo = null; + mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; + mUids = null; + mAdministratorUids = new int[0]; + mOwnerUid = Process.INVALID_UID; + mSSID = null; + mPrivateDnsBroken = false; + mRequestorUid = Process.INVALID_UID; + mRequestorPackageName = null; + } + + /** + * Set all contents of this object to the contents of a NetworkCapabilities. + * + * @param nc Original NetworkCapabilities + * @hide + */ + public void set(@NonNull NetworkCapabilities nc) { + mNetworkCapabilities = nc.mNetworkCapabilities; + mTransportTypes = nc.mTransportTypes; + mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps; + mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps; + mNetworkSpecifier = nc.mNetworkSpecifier; + if (nc.getTransportInfo() != null) { + setTransportInfo(nc.getTransportInfo().makeCopy(mParcelLocationSensitiveFields)); + } else { + setTransportInfo(null); + } + mSignalStrength = nc.mSignalStrength; + setUids(nc.mUids); // Will make the defensive copy + setAdministratorUids(nc.getAdministratorUids()); + mOwnerUid = nc.mOwnerUid; + mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; + mSSID = nc.mSSID; + mPrivateDnsBroken = nc.mPrivateDnsBroken; + mRequestorUid = nc.mRequestorUid; + mRequestorPackageName = nc.mRequestorPackageName; + } + + /** + * Represents the network's capabilities. If any are specified they will be satisfied + * by any Network that matches all of them. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private long mNetworkCapabilities; + + /** + * If any capabilities specified here they must not exist in the matching Network. + */ + private long mUnwantedNetworkCapabilities; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "NET_CAPABILITY_" }, value = { + NET_CAPABILITY_MMS, + NET_CAPABILITY_SUPL, + NET_CAPABILITY_DUN, + NET_CAPABILITY_FOTA, + NET_CAPABILITY_IMS, + NET_CAPABILITY_CBS, + NET_CAPABILITY_WIFI_P2P, + NET_CAPABILITY_IA, + NET_CAPABILITY_RCS, + NET_CAPABILITY_XCAP, + NET_CAPABILITY_EIMS, + NET_CAPABILITY_NOT_METERED, + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_NOT_RESTRICTED, + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_VALIDATED, + NET_CAPABILITY_CAPTIVE_PORTAL, + NET_CAPABILITY_NOT_ROAMING, + NET_CAPABILITY_FOREGROUND, + NET_CAPABILITY_NOT_CONGESTED, + NET_CAPABILITY_NOT_SUSPENDED, + NET_CAPABILITY_OEM_PAID, + NET_CAPABILITY_MCX, + NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NET_CAPABILITY_TEMPORARILY_NOT_METERED, + NET_CAPABILITY_OEM_PRIVATE, + NET_CAPABILITY_VEHICLE_INTERNAL, + NET_CAPABILITY_NOT_VCN_MANAGED, + }) + public @interface NetCapability { } + + /** + * Indicates this is a network that has the ability to reach the + * carrier's MMSC for sending and receiving MMS messages. + */ + public static final int NET_CAPABILITY_MMS = 0; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * SUPL server, used to retrieve GPS information. + */ + public static final int NET_CAPABILITY_SUPL = 1; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * DUN or tethering gateway. + */ + public static final int NET_CAPABILITY_DUN = 2; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * FOTA portal, used for over the air updates. + */ + public static final int NET_CAPABILITY_FOTA = 3; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * IMS servers, used for network registration and signaling. + */ + public static final int NET_CAPABILITY_IMS = 4; + + /** + * Indicates this is a network that has the ability to reach the carrier's + * CBS servers, used for carrier specific services. + */ + public static final int NET_CAPABILITY_CBS = 5; + + /** + * Indicates this is a network that has the ability to reach a Wi-Fi direct + * peer. + */ + public static final int NET_CAPABILITY_WIFI_P2P = 6; + + /** + * Indicates this is a network that has the ability to reach a carrier's + * Initial Attach servers. + */ + public static final int NET_CAPABILITY_IA = 7; + + /** + * Indicates this is a network that has the ability to reach a carrier's + * RCS servers, used for Rich Communication Services. + */ + public static final int NET_CAPABILITY_RCS = 8; + + /** + * Indicates this is a network that has the ability to reach a carrier's + * XCAP servers, used for configuration and control. + */ + public static final int NET_CAPABILITY_XCAP = 9; + + /** + * Indicates this is a network that has the ability to reach a carrier's + * Emergency IMS servers or other services, used for network signaling + * during emergency calls. + */ + public static final int NET_CAPABILITY_EIMS = 10; + + /** + * Indicates that this network is unmetered. + */ + public static final int NET_CAPABILITY_NOT_METERED = 11; + + /** + * Indicates that this network should be able to reach the internet. + */ + public static final int NET_CAPABILITY_INTERNET = 12; + + /** + * Indicates that this network is available for general use. If this is not set + * applications should not attempt to communicate on this network. Note that this + * is simply informative and not enforcement - enforcement is handled via other means. + * Set by default. + */ + public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; + + /** + * Indicates that the user has indicated implicit trust of this network. This + * generally means it's a sim-selected carrier, a plugged in ethernet, a paired + * BT device or a wifi the user asked to connect to. Untrusted networks + * are probably limited to unknown wifi AP. Set by default. + */ + public static final int NET_CAPABILITY_TRUSTED = 14; + + /** + * Indicates that this network is not a VPN. This capability is set by default and should be + * explicitly cleared for VPN networks. + */ + public static final int NET_CAPABILITY_NOT_VPN = 15; + + /** + * Indicates that connectivity on this network was successfully validated. For example, for a + * network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was successfully + * detected. + */ + public static final int NET_CAPABILITY_VALIDATED = 16; + + /** + * Indicates that this network was found to have a captive portal in place last time it was + * probed. + */ + public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; + + /** + * Indicates that this network is not roaming. + */ + public static final int NET_CAPABILITY_NOT_ROAMING = 18; + + /** + * Indicates that this network is available for use by apps, and not a network that is being + * kept up in the background to facilitate fast network switching. + */ + public static final int NET_CAPABILITY_FOREGROUND = 19; + + /** + * Indicates that this network is not congested. + *

+ * When a network is congested, applications should defer network traffic + * that can be done at a later time, such as uploading analytics. + */ + public static final int NET_CAPABILITY_NOT_CONGESTED = 20; + + /** + * Indicates that this network is not currently suspended. + *

+ * When a network is suspended, the network's IP addresses and any connections + * established on the network remain valid, but the network is temporarily unable + * to transfer data. This can happen, for example, if a cellular network experiences + * a temporary loss of signal, such as when driving through a tunnel, etc. + * A network with this capability is not suspended, so is expected to be able to + * transfer data. + */ + public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; + + /** + * Indicates that traffic that goes through this network is paid by oem. For example, + * this network can be used by system apps to upload telemetry data. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_OEM_PAID = 22; + + /** + * Indicates this is a network that has the ability to reach a carrier's Mission Critical + * servers. + */ + public static final int NET_CAPABILITY_MCX = 23; + + /** + * Indicates that this network was tested to only provide partial connectivity. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; + + /** + * Indicates that this network is temporarily unmetered. + *

+ * This capability will be set for networks that are generally metered, but are currently + * unmetered, e.g., because the user is in a particular area. This capability can be changed at + * any time. When it is removed, applications are responsible for stopping any data transfer + * that should not occur on a metered network. + * Note that most apps should use {@link #NET_CAPABILITY_NOT_METERED} instead. For more + * information, see https://developer.android.com/about/versions/11/features/5g#meteredness. + */ + public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; + + /** + * Indicates that this network is private to the OEM and meant only for OEM use. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_OEM_PRIVATE = 26; + + /** + * Indicates this is an internal vehicle network, meant to communicate with other + * automotive systems. + * + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; + + /** + * Indicates that this network is not managed by a Virtual Carrier Network (VCN). + * + * TODO(b/177299683): Add additional clarifying javadoc. + * @hide + */ + public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; + + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VCN_MANAGED; + + /** + * Network capabilities that are expected to be mutable, i.e., can change while a particular + * network is connected. + */ + private static final long MUTABLE_CAPABILITIES = + // TRUSTED can change when user explicitly connects to an untrusted network in Settings. + // http://b/18206275 + (1 << NET_CAPABILITY_TRUSTED) + | (1 << NET_CAPABILITY_VALIDATED) + | (1 << NET_CAPABILITY_CAPTIVE_PORTAL) + | (1 << NET_CAPABILITY_NOT_ROAMING) + | (1 << NET_CAPABILITY_FOREGROUND) + | (1 << NET_CAPABILITY_NOT_CONGESTED) + | (1 << NET_CAPABILITY_NOT_SUSPENDED) + | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY) + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); + + /** + * Network capabilities that are not allowed in NetworkRequests. This exists because the + * NetworkFactory / NetworkAgent model does not deal well with the situation where a + * capability's presence cannot be known in advance. If such a capability is requested, then we + * can get into a cycle where the NetworkFactory endlessly churns out NetworkAgents that then + * get immediately torn down because they do not have the requested capability. + */ + // Note that as a historical exception, the TRUSTED and NOT_VCN_MANAGED capabilities + // are mutable but requestable. Factories are responsible for not getting + // in an infinite loop about these. + private static final long NON_REQUESTABLE_CAPABILITIES = + MUTABLE_CAPABILITIES + & ~(1 << NET_CAPABILITY_TRUSTED) + & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED); + + /** + * Capabilities that are set by default when the object is constructed. + */ + private static final long DEFAULT_CAPABILITIES = + (1 << NET_CAPABILITY_NOT_RESTRICTED) + | (1 << NET_CAPABILITY_TRUSTED) + | (1 << NET_CAPABILITY_NOT_VPN); + + /** + * Capabilities that suggest that a network is restricted. + * {@see #maybeMarkCapabilitiesRestricted}, {@see #FORCE_RESTRICTED_CAPABILITIES} + */ + @VisibleForTesting + /* package */ static final long RESTRICTED_CAPABILITIES = + (1 << NET_CAPABILITY_CBS) + | (1 << NET_CAPABILITY_DUN) + | (1 << NET_CAPABILITY_EIMS) + | (1 << NET_CAPABILITY_FOTA) + | (1 << NET_CAPABILITY_IA) + | (1 << NET_CAPABILITY_IMS) + | (1 << NET_CAPABILITY_MCX) + | (1 << NET_CAPABILITY_RCS) + | (1 << NET_CAPABILITY_VEHICLE_INTERNAL) + | (1 << NET_CAPABILITY_XCAP); + + /** + * Capabilities that force network to be restricted. + * {@see #maybeMarkCapabilitiesRestricted}. + */ + private static final long FORCE_RESTRICTED_CAPABILITIES = + (1 << NET_CAPABILITY_OEM_PAID) + | (1 << NET_CAPABILITY_OEM_PRIVATE); + + /** + * Capabilities that suggest that a network is unrestricted. + * {@see #maybeMarkCapabilitiesRestricted}. + */ + @VisibleForTesting + /* package */ static final long UNRESTRICTED_CAPABILITIES = + (1 << NET_CAPABILITY_INTERNET) + | (1 << NET_CAPABILITY_MMS) + | (1 << NET_CAPABILITY_SUPL) + | (1 << NET_CAPABILITY_WIFI_P2P); + + /** + * Capabilities that are managed by ConnectivityService. + */ + private static final long CONNECTIVITY_MANAGED_CAPABILITIES = + (1 << NET_CAPABILITY_VALIDATED) + | (1 << NET_CAPABILITY_CAPTIVE_PORTAL) + | (1 << NET_CAPABILITY_FOREGROUND) + | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY); + + /** + * Capabilities that are allowed for test networks. This list must be set so that it is safe + * for an unprivileged user to create a network with these capabilities via shell. As such, + * it must never contain capabilities that are generally useful to the system, such as + * INTERNET, IMS, SUPL, etc. + */ + private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES = + (1 << NET_CAPABILITY_NOT_METERED) + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) + | (1 << NET_CAPABILITY_NOT_RESTRICTED) + | (1 << NET_CAPABILITY_NOT_VPN) + | (1 << NET_CAPABILITY_NOT_ROAMING) + | (1 << NET_CAPABILITY_NOT_CONGESTED) + | (1 << NET_CAPABILITY_NOT_SUSPENDED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); + + /** + * Adds the given capability to this {@code NetworkCapability} instance. + * Note that when searching for a network to satisfy a request, all capabilities + * requested must be satisfied. + * + * @param capability the capability to be added. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities addCapability(@NetCapability int capability) { + // If the given capability was previously added to the list of unwanted capabilities + // then the capability will also be removed from the list of unwanted capabilities. + // TODO: Consider adding unwanted capabilities to the public API and mention this + // in the documentation. + checkValidCapability(capability); + mNetworkCapabilities |= 1 << capability; + mUnwantedNetworkCapabilities &= ~(1 << capability); // remove from unwanted capability list + return this; + } + + /** + * Adds the given capability to the list of unwanted capabilities of this + * {@code NetworkCapability} instance. Note that when searching for a network to + * satisfy a request, the network must not contain any capability from unwanted capability + * list. + *

+ * If the capability was previously added to the list of required capabilities (for + * example, it was there by default or added using {@link #addCapability(int)} method), then + * it will be removed from the list of required capabilities as well. + * + * @see #addCapability(int) + * @hide + */ + public void addUnwantedCapability(@NetCapability int capability) { + checkValidCapability(capability); + mUnwantedNetworkCapabilities |= 1 << capability; + mNetworkCapabilities &= ~(1 << capability); // remove from requested capabilities + } + + /** + * Removes (if found) the given capability from this {@code NetworkCapability} instance. + * + * @param capability the capability to be removed. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) { + // Note that this method removes capabilities that were added via addCapability(int), + // addUnwantedCapability(int) or setCapabilities(int[], int[]). + checkValidCapability(capability); + final long mask = ~(1 << capability); + mNetworkCapabilities &= mask; + mUnwantedNetworkCapabilities &= mask; + return this; + } + + /** + * Sets (or clears) the given capability on this {@link NetworkCapabilities} + * instance. + * @hide + */ + public @NonNull NetworkCapabilities setCapability(@NetCapability int capability, + boolean value) { + if (value) { + addCapability(capability); + } else { + removeCapability(capability); + } + return this; + } + + /** + * Gets all the capabilities set on this {@code NetworkCapability} instance. + * + * @return an array of capability values for this instance. + * @hide + */ + @UnsupportedAppUsage + public @NetCapability int[] getCapabilities() { + return BitUtils.unpackBits(mNetworkCapabilities); + } + + /** + * Gets all the unwanted capabilities set on this {@code NetworkCapability} instance. + * + * @return an array of unwanted capability values for this instance. + * @hide + */ + public @NetCapability int[] getUnwantedCapabilities() { + return BitUtils.unpackBits(mUnwantedNetworkCapabilities); + } + + + /** + * Sets all the capabilities set on this {@code NetworkCapability} instance. + * This overwrites any existing capabilities. + * + * @hide + */ + public void setCapabilities(@NetCapability int[] capabilities, + @NetCapability int[] unwantedCapabilities) { + mNetworkCapabilities = BitUtils.packBits(capabilities); + mUnwantedNetworkCapabilities = BitUtils.packBits(unwantedCapabilities); + } + + /** + * @deprecated use {@link #setCapabilities(int[], int[])} + * @hide + */ + @Deprecated + public void setCapabilities(@NetCapability int[] capabilities) { + setCapabilities(capabilities, new int[] {}); + } + + /** + * Tests for the presence of a capability on this instance. + * + * @param capability the capabilities to be tested for. + * @return {@code true} if set on this instance. + */ + public boolean hasCapability(@NetCapability int capability) { + return isValidCapability(capability) + && ((mNetworkCapabilities & (1 << capability)) != 0); + } + + /** @hide */ + public boolean hasUnwantedCapability(@NetCapability int capability) { + return isValidCapability(capability) + && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0); + } + + /** + * Check if this NetworkCapabilities has system managed capabilities or not. + * @hide + */ + public boolean hasConnectivityManagedCapability() { + return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0); + } + + /** Note this method may result in having the same capability in wanted and unwanted lists. */ + private void combineNetCapabilities(@NonNull NetworkCapabilities nc) { + this.mNetworkCapabilities |= nc.mNetworkCapabilities; + this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities; + } + + /** + * Convenience function that returns a human-readable description of the first mutable + * capability we find. Used to present an error message to apps that request mutable + * capabilities. + * + * @hide + */ + public @Nullable String describeFirstNonRequestableCapability() { + final long nonRequestable = (mNetworkCapabilities | mUnwantedNetworkCapabilities) + & NON_REQUESTABLE_CAPABILITIES; + + if (nonRequestable != 0) { + return capabilityNameOf(BitUtils.unpackBits(nonRequestable)[0]); + } + if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth"; + if (hasSignalStrength()) return "signalStrength"; + if (isPrivateDnsBroken()) { + return "privateDnsBroken"; + } + return null; + } + + private boolean satisfiedByNetCapabilities(@NonNull NetworkCapabilities nc, + boolean onlyImmutable) { + long requestedCapabilities = mNetworkCapabilities; + long requestedUnwantedCapabilities = mUnwantedNetworkCapabilities; + long providedCapabilities = nc.mNetworkCapabilities; + + if (onlyImmutable) { + requestedCapabilities &= ~MUTABLE_CAPABILITIES; + requestedUnwantedCapabilities &= ~MUTABLE_CAPABILITIES; + } + return ((providedCapabilities & requestedCapabilities) == requestedCapabilities) + && ((requestedUnwantedCapabilities & providedCapabilities) == 0); + } + + /** @hide */ + public boolean equalsNetCapabilities(@NonNull NetworkCapabilities nc) { + return (nc.mNetworkCapabilities == this.mNetworkCapabilities) + && (nc.mUnwantedNetworkCapabilities == this.mUnwantedNetworkCapabilities); + } + + private boolean equalsNetCapabilitiesRequestable(@NonNull NetworkCapabilities that) { + return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) == + (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)) + && ((this.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) == + (that.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)); + } + + /** + * Deduces that all the capabilities it provides are typically provided by restricted networks + * or not. + * + * @return {@code true} if the network should be restricted. + * @hide + */ + public boolean deduceRestrictedCapability() { + // Check if we have any capability that forces the network to be restricted. + final boolean forceRestrictedCapability = + (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0; + + // Verify there aren't any unrestricted capabilities. If there are we say + // the whole thing is unrestricted unless it is forced to be restricted. + final boolean hasUnrestrictedCapabilities = + (mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0; + + // Must have at least some restricted capabilities. + final boolean hasRestrictedCapabilities = + (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0; + + return forceRestrictedCapability + || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities); + } + + /** + * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted. + * + * @hide + */ + public void maybeMarkCapabilitiesRestricted() { + if (deduceRestrictedCapability()) { + removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + } + } + + /** + * Test networks have strong restrictions on what capabilities they can have. Enforce these + * restrictions. + * @hide + */ + public void restrictCapabilitesForTestNetwork(int creatorUid) { + final long originalCapabilities = mNetworkCapabilities; + final long originalTransportTypes = mTransportTypes; + final NetworkSpecifier originalSpecifier = mNetworkSpecifier; + final int originalSignalStrength = mSignalStrength; + final int originalOwnerUid = getOwnerUid(); + final int[] originalAdministratorUids = getAdministratorUids(); + clearAll(); + mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS) + | (1 << TRANSPORT_TEST); + mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES; + mNetworkSpecifier = originalSpecifier; + mSignalStrength = originalSignalStrength; + + // Only retain the owner and administrator UIDs if they match the app registering the remote + // caller that registered the network. + if (originalOwnerUid == creatorUid) { + setOwnerUid(creatorUid); + } + if (ArrayUtils.contains(originalAdministratorUids, creatorUid)) { + setAdministratorUids(new int[] {creatorUid}); + } + // There is no need to clear the UIDs, they have already been cleared by clearAll() above. + } + + /** + * Representing the transport type. Apps should generally not care about transport. A + * request for a fast internet connection could be satisfied by a number of different + * transports. If any are specified here it will be satisfied a Network that matches + * any of them. If a caller doesn't care about the transport it should not specify any. + */ + private long mTransportTypes; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TRANSPORT_" }, value = { + TRANSPORT_CELLULAR, + TRANSPORT_WIFI, + TRANSPORT_BLUETOOTH, + TRANSPORT_ETHERNET, + TRANSPORT_VPN, + TRANSPORT_WIFI_AWARE, + TRANSPORT_LOWPAN, + TRANSPORT_TEST, + }) + public @interface Transport { } + + /** + * Indicates this network uses a Cellular transport. + */ + public static final int TRANSPORT_CELLULAR = 0; + + /** + * Indicates this network uses a Wi-Fi transport. + */ + public static final int TRANSPORT_WIFI = 1; + + /** + * Indicates this network uses a Bluetooth transport. + */ + public static final int TRANSPORT_BLUETOOTH = 2; + + /** + * Indicates this network uses an Ethernet transport. + */ + public static final int TRANSPORT_ETHERNET = 3; + + /** + * Indicates this network uses a VPN transport. + */ + public static final int TRANSPORT_VPN = 4; + + /** + * Indicates this network uses a Wi-Fi Aware transport. + */ + public static final int TRANSPORT_WIFI_AWARE = 5; + + /** + * Indicates this network uses a LoWPAN transport. + */ + public static final int TRANSPORT_LOWPAN = 6; + + /** + * Indicates this network uses a Test-only virtual interface as a transport. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int TRANSPORT_TEST = 7; + + /** @hide */ + public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR; + /** @hide */ + public static final int MAX_TRANSPORT = TRANSPORT_TEST; + + /** @hide */ + public static boolean isValidTransport(@Transport int transportType) { + return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT); + } + + private static final String[] TRANSPORT_NAMES = { + "CELLULAR", + "WIFI", + "BLUETOOTH", + "ETHERNET", + "VPN", + "WIFI_AWARE", + "LOWPAN", + "TEST" + }; + + /** + * Allowed transports on a test network, in addition to TRANSPORT_TEST. + */ + private static final int TEST_NETWORKS_ALLOWED_TRANSPORTS = 1 << TRANSPORT_TEST + // Test ethernet networks can be created with EthernetManager#setIncludeTestInterfaces + | 1 << TRANSPORT_ETHERNET + // Test VPN networks can be created but their UID ranges must be empty. + | 1 << TRANSPORT_VPN; + + /** + * Adds the given transport type to this {@code NetworkCapability} instance. + * Multiple transports may be applied. Note that when searching + * for a network to satisfy a request, any listed in the request will satisfy the request. + * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a + * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network + * to be selected. This is logically different than + * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above. + * + * @param transportType the transport type to be added. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities addTransportType(@Transport int transportType) { + checkValidTransportType(transportType); + mTransportTypes |= 1 << transportType; + setNetworkSpecifier(mNetworkSpecifier); // used for exception checking + return this; + } + + /** + * Removes (if found) the given transport from this {@code NetworkCapability} instance. + * + * @param transportType the transport type to be removed. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities removeTransportType(@Transport int transportType) { + checkValidTransportType(transportType); + mTransportTypes &= ~(1 << transportType); + setNetworkSpecifier(mNetworkSpecifier); // used for exception checking + return this; + } + + /** + * Sets (or clears) the given transport on this {@link NetworkCapabilities} + * instance. + * + * @hide + */ + public @NonNull NetworkCapabilities setTransportType(@Transport int transportType, + boolean value) { + if (value) { + addTransportType(transportType); + } else { + removeTransportType(transportType); + } + return this; + } + + /** + * Gets all the transports set on this {@code NetworkCapability} instance. + * + * @return an array of transport type values for this instance. + * @hide + */ + @SystemApi + @NonNull public @Transport int[] getTransportTypes() { + return BitUtils.unpackBits(mTransportTypes); + } + + /** + * Sets all the transports set on this {@code NetworkCapability} instance. + * This overwrites any existing transports. + * + * @hide + */ + public void setTransportTypes(@Transport int[] transportTypes) { + mTransportTypes = BitUtils.packBits(transportTypes); + } + + /** + * Tests for the presence of a transport on this instance. + * + * @param transportType the transport type to be tested for. + * @return {@code true} if set on this instance. + */ + public boolean hasTransport(@Transport int transportType) { + return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0); + } + + private void combineTransportTypes(NetworkCapabilities nc) { + this.mTransportTypes |= nc.mTransportTypes; + } + + private boolean satisfiedByTransportTypes(NetworkCapabilities nc) { + return ((this.mTransportTypes == 0) + || ((this.mTransportTypes & nc.mTransportTypes) != 0)); + } + + /** @hide */ + public boolean equalsTransportTypes(NetworkCapabilities nc) { + return (nc.mTransportTypes == this.mTransportTypes); + } + + /** + * UID of the app that owns this network, or Process#INVALID_UID if none/unknown. + * + *

This field keeps track of the UID of the app that created this network and is in charge of + * its lifecycle. This could be the UID of apps such as the Wifi network suggestor, the running + * VPN, or Carrier Service app managing a cellular data connection. + * + *

For NetworkCapability instances being sent from ConnectivityService, this value MUST be + * reset to Process.INVALID_UID unless all the following conditions are met: + * + *

The caller is the network owner, AND one of the following sets of requirements is met: + * + *

    + *
  1. The described Network is a VPN + *
+ * + *

OR: + * + *

    + *
  1. The calling app is the network owner + *
  2. The calling app has the ACCESS_FINE_LOCATION permission granted + *
  3. The user's location toggle is on + *
+ * + * This is because the owner UID is location-sensitive. The apps that request a network could + * know where the device is if they can tell for sure the system has connected to the network + * they requested. + * + *

This is populated by the network agents and for the NetworkCapabilities instance sent by + * an app to the System Server, the value MUST be reset to Process.INVALID_UID by the system + * server. + */ + private int mOwnerUid = Process.INVALID_UID; + + /** + * Set the UID of the owner app. + * @hide + */ + public @NonNull NetworkCapabilities setOwnerUid(final int uid) { + mOwnerUid = uid; + return this; + } + + /** + * Retrieves the UID of the app that owns this network. + * + *

For user privacy reasons, this field will only be populated if the following conditions + * are met: + * + *

The caller is the network owner, AND one of the following sets of requirements is met: + * + *

    + *
  1. The described Network is a VPN + *
+ * + *

OR: + * + *

    + *
  1. The calling app is the network owner + *
  2. The calling app has the ACCESS_FINE_LOCATION permission granted + *
  3. The user's location toggle is on + *
+ * + * Instances of NetworkCapabilities sent to apps without the appropriate permissions will have + * this field cleared out. + */ + public int getOwnerUid() { + return mOwnerUid; + } + + private boolean equalsOwnerUid(@NonNull final NetworkCapabilities nc) { + return mOwnerUid == nc.mOwnerUid; + } + + /** + * UIDs of packages that are administrators of this network, or empty if none. + * + *

This field tracks the UIDs of packages that have permission to manage this network. + * + *

Network owners will also be listed as administrators. + * + *

For NetworkCapability instances being sent from the System Server, this value MUST be + * empty unless the destination is 1) the System Server, or 2) Telephony. In either case, the + * receiving entity must have the ACCESS_FINE_LOCATION permission and target R+. + * + *

When received from an app in a NetworkRequest this is always cleared out by the system + * server. This field is never used for matching NetworkRequests to NetworkAgents. + */ + @NonNull private int[] mAdministratorUids = new int[0]; + + /** + * Sets the int[] of UIDs that are administrators of this network. + * + *

UIDs included in administratorUids gain administrator privileges over this Network. + * Examples of UIDs that should be included in administratorUids are: + * + *

    + *
  • Carrier apps with privileges for the relevant subscription + *
  • Active VPN apps + *
  • Other application groups with a particular Network-related role + *
+ * + *

In general, user-supplied networks (such as WiFi networks) do not have an administrator. + * + *

An app is granted owner privileges over Networks that it supplies. The owner UID MUST + * always be included in administratorUids. + * + *

The administrator UIDs are set by network agents. + * + * @param administratorUids the UIDs to be set as administrators of this Network. + * @throws IllegalArgumentException if duplicate UIDs are contained in administratorUids + * @see #mAdministratorUids + * @hide + */ + @NonNull + public NetworkCapabilities setAdministratorUids(@NonNull final int[] administratorUids) { + mAdministratorUids = Arrays.copyOf(administratorUids, administratorUids.length); + Arrays.sort(mAdministratorUids); + for (int i = 0; i < mAdministratorUids.length - 1; i++) { + if (mAdministratorUids[i] >= mAdministratorUids[i + 1]) { + throw new IllegalArgumentException("All administrator UIDs must be unique"); + } + } + return this; + } + + /** + * Retrieves the UIDs that are administrators of this Network. + * + *

This is only populated in NetworkCapabilities objects that come from network agents for + * networks that are managed by specific apps on the system, such as carrier privileged apps or + * wifi suggestion apps. This will include the network owner. + * + * @return the int[] of UIDs that are administrators of this Network + * @see #mAdministratorUids + * @hide + */ + @NonNull + @SystemApi + public int[] getAdministratorUids() { + return Arrays.copyOf(mAdministratorUids, mAdministratorUids.length); + } + + /** + * Tests if the set of administrator UIDs of this network is the same as that of the passed one. + * + *

The administrator UIDs must be in sorted order. + * + *

nc is assumed non-null. Else, NPE. + * + * @hide + */ + @VisibleForTesting(visibility = PRIVATE) + public boolean equalsAdministratorUids(@NonNull final NetworkCapabilities nc) { + return Arrays.equals(mAdministratorUids, nc.mAdministratorUids); + } + + /** + * Combine the administrator UIDs of the capabilities. + * + *

This is only legal if either of the administrators lists are empty, or if they are equal. + * Combining administrator UIDs is only possible for combining non-overlapping sets of UIDs. + * + *

If both administrator lists are non-empty but not equal, they conflict with each other. In + * this case, it would not make sense to add them together. + */ + private void combineAdministratorUids(@NonNull final NetworkCapabilities nc) { + if (nc.mAdministratorUids.length == 0) return; + if (mAdministratorUids.length == 0) { + mAdministratorUids = Arrays.copyOf(nc.mAdministratorUids, nc.mAdministratorUids.length); + return; + } + if (!equalsAdministratorUids(nc)) { + throw new IllegalStateException("Can't combine two different administrator UID lists"); + } + } + + /** + * Value indicating that link bandwidth is unspecified. + * @hide + */ + public static final int LINK_BANDWIDTH_UNSPECIFIED = 0; + + /** + * Passive link bandwidth. This is a rough guide of the expected peak bandwidth + * for the first hop on the given transport. It is not measured, but may take into account + * link parameters (Radio technology, allocated channels, etc). + */ + private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + + /** + * Sets the upstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + *

+ * {@see Builder#setLinkUpstreamBandwidthKbps} + * + * @param upKbps the estimated first hop upstream (device to network) bandwidth. + * @hide + */ + public @NonNull NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) { + mLinkUpBandwidthKbps = upKbps; + return this; + } + + /** + * Retrieves the upstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + * + * @return The estimated first hop upstream (device to network) bandwidth. + */ + public int getLinkUpstreamBandwidthKbps() { + return mLinkUpBandwidthKbps; + } + + /** + * Sets the downstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + *

+ * {@see Builder#setLinkUpstreamBandwidthKbps} + * + * @param downKbps the estimated first hop downstream (network to device) bandwidth. + * @hide + */ + public @NonNull NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) { + mLinkDownBandwidthKbps = downKbps; + return this; + } + + /** + * Retrieves the downstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + * + * @return The estimated first hop downstream (network to device) bandwidth. + */ + public int getLinkDownstreamBandwidthKbps() { + return mLinkDownBandwidthKbps; + } + + private void combineLinkBandwidths(NetworkCapabilities nc) { + this.mLinkUpBandwidthKbps = + Math.max(this.mLinkUpBandwidthKbps, nc.mLinkUpBandwidthKbps); + this.mLinkDownBandwidthKbps = + Math.max(this.mLinkDownBandwidthKbps, nc.mLinkDownBandwidthKbps); + } + private boolean satisfiedByLinkBandwidths(NetworkCapabilities nc) { + return !(this.mLinkUpBandwidthKbps > nc.mLinkUpBandwidthKbps + || this.mLinkDownBandwidthKbps > nc.mLinkDownBandwidthKbps); + } + private boolean equalsLinkBandwidths(NetworkCapabilities nc) { + return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps + && this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps); + } + /** @hide */ + public static int minBandwidth(int a, int b) { + if (a == LINK_BANDWIDTH_UNSPECIFIED) { + return b; + } else if (b == LINK_BANDWIDTH_UNSPECIFIED) { + return a; + } else { + return Math.min(a, b); + } + } + /** @hide */ + public static int maxBandwidth(int a, int b) { + return Math.max(a, b); + } + + private NetworkSpecifier mNetworkSpecifier = null; + private TransportInfo mTransportInfo = null; + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + *

+ * + * @param networkSpecifier A concrete, parcelable framework class that extends + * NetworkSpecifier. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities setNetworkSpecifier( + @NonNull NetworkSpecifier networkSpecifier) { + if (networkSpecifier != null && Long.bitCount(mTransportTypes) != 1) { + throw new IllegalStateException("Must have a single transport specified to use " + + "setNetworkSpecifier"); + } + + mNetworkSpecifier = networkSpecifier; + + return this; + } + + /** + * Sets the optional transport specific information. + * + * @param transportInfo A concrete, parcelable framework class that extends + * {@link TransportInfo}. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities setTransportInfo(@NonNull TransportInfo transportInfo) { + mTransportInfo = transportInfo; + return this; + } + + /** + * Gets the optional bearer specific network specifier. May be {@code null} if not set. + * + * @return The optional {@link NetworkSpecifier} specifying the bearer specific network + * specifier or {@code null}. + */ + public @Nullable NetworkSpecifier getNetworkSpecifier() { + return mNetworkSpecifier; + } + + /** + * Returns a transport-specific information container. The application may cast this + * container to a concrete sub-class based on its knowledge of the network request. The + * application should be able to deal with a {@code null} return value or an invalid case, + * e.g. use {@code instanceof} operator to verify expected type. + * + * @return A concrete implementation of the {@link TransportInfo} class or null if not + * available for the network. + */ + @Nullable public TransportInfo getTransportInfo() { + return mTransportInfo; + } + + private void combineSpecifiers(NetworkCapabilities nc) { + if (mNetworkSpecifier != null && !mNetworkSpecifier.equals(nc.mNetworkSpecifier)) { + throw new IllegalStateException("Can't combine two networkSpecifiers"); + } + setNetworkSpecifier(nc.mNetworkSpecifier); + } + + private boolean satisfiedBySpecifier(NetworkCapabilities nc) { + return mNetworkSpecifier == null || mNetworkSpecifier.canBeSatisfiedBy(nc.mNetworkSpecifier) + || nc.mNetworkSpecifier instanceof MatchAllNetworkSpecifier; + } + + private boolean equalsSpecifier(NetworkCapabilities nc) { + return Objects.equals(mNetworkSpecifier, nc.mNetworkSpecifier); + } + + private void combineTransportInfos(NetworkCapabilities nc) { + if (mTransportInfo != null && !mTransportInfo.equals(nc.mTransportInfo)) { + throw new IllegalStateException("Can't combine two TransportInfos"); + } + setTransportInfo(nc.mTransportInfo); + } + + private boolean equalsTransportInfo(NetworkCapabilities nc) { + return Objects.equals(mTransportInfo, nc.mTransportInfo); + } + + /** + * Magic value that indicates no signal strength provided. A request specifying this value is + * always satisfied. + */ + public static final int SIGNAL_STRENGTH_UNSPECIFIED = Integer.MIN_VALUE; + + /** + * Signal strength. This is a signed integer, and higher values indicate better signal. + * The exact units are bearer-dependent. For example, Wi-Fi uses RSSI. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + private int mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; + + /** + * Sets the signal strength. This is a signed integer, with higher values indicating a stronger + * signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same RSSI units + * reported by wifi code. + *

+ * Note that when used to register a network callback, this specifies the minimum acceptable + * signal strength. When received as the state of an existing network it specifies the current + * value. A value of code SIGNAL_STRENGTH_UNSPECIFIED} means no value when received and has no + * effect when requesting a callback. + * + * @param signalStrength the bearer-specific signal strength. + * @hide + */ + public @NonNull NetworkCapabilities setSignalStrength(int signalStrength) { + mSignalStrength = signalStrength; + return this; + } + + /** + * Returns {@code true} if this object specifies a signal strength. + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public boolean hasSignalStrength() { + return mSignalStrength > SIGNAL_STRENGTH_UNSPECIFIED; + } + + /** + * Retrieves the signal strength. + * + * @return The bearer-specific signal strength. + */ + public int getSignalStrength() { + return mSignalStrength; + } + + private void combineSignalStrength(NetworkCapabilities nc) { + this.mSignalStrength = Math.max(this.mSignalStrength, nc.mSignalStrength); + } + + private boolean satisfiedBySignalStrength(NetworkCapabilities nc) { + return this.mSignalStrength <= nc.mSignalStrength; + } + + private boolean equalsSignalStrength(NetworkCapabilities nc) { + return this.mSignalStrength == nc.mSignalStrength; + } + + /** + * List of UIDs this network applies to. No restriction if null. + *

+ * For networks, mUids represent the list of network this applies to, and null means this + * network applies to all UIDs. + * For requests, mUids is the list of UIDs this network MUST apply to to match ; ALL UIDs + * must be included in a network so that they match. As an exception to the general rule, + * a null mUids field for requests mean "no requirements" rather than what the general rule + * would suggest ("must apply to all UIDs") : this is because this has shown to be what users + * of this API expect in practice. A network that must match all UIDs can still be + * expressed with a set ranging the entire set of possible UIDs. + *

+ * mUids is typically (and at this time, only) used by VPN. This network is only available to + * the UIDs in this list, and it is their default network. Apps in this list that wish to + * bypass the VPN can do so iff the VPN app allows them to or if they are privileged. If this + * member is null, then the network is not restricted by app UID. If it's an empty list, then + * it means nobody can use it. + * As a special exception, the app managing this network (as identified by its UID stored in + * mOwnerUid) can always see this network. This is embodied by a special check in + * satisfiedByUids. That still does not mean the network necessarily applies + * to the app that manages it as determined by #appliesToUid. + *

+ * Please note that in principle a single app can be associated with multiple UIDs because + * each app will have a different UID when it's run as a different (macro-)user. A single + * macro user can only have a single active VPN app at any given time however. + *

+ * Also please be aware this class does not try to enforce any normalization on this. Callers + * can only alter the UIDs by setting them wholesale : this class does not provide any utility + * to add or remove individual UIDs or ranges. If callers have any normalization needs on + * their own (like requiring sortedness or no overlap) they need to enforce it + * themselves. Some of the internal methods also assume this is normalized as in no adjacent + * or overlapping ranges are present. + * + * @hide + */ + private ArraySet mUids = null; + + /** + * Convenience method to set the UIDs this network applies to to a single UID. + * @hide + */ + public @NonNull NetworkCapabilities setSingleUid(int uid) { + final ArraySet identity = new ArraySet<>(1); + identity.add(new UidRange(uid, uid)); + setUids(identity); + return this; + } + + /** + * Set the list of UIDs this network applies to. + * This makes a copy of the set so that callers can't modify it after the call. + * @hide + */ + public @NonNull NetworkCapabilities setUids(Set uids) { + if (null == uids) { + mUids = null; + } else { + mUids = new ArraySet<>(uids); + } + return this; + } + + /** + * Get the list of UIDs this network applies to. + * This returns a copy of the set so that callers can't modify the original object. + * @hide + */ + public @Nullable Set getUids() { + return null == mUids ? null : new ArraySet<>(mUids); + } + + /** + * Test whether this network applies to this UID. + * @hide + */ + public boolean appliesToUid(int uid) { + if (null == mUids) return true; + for (UidRange range : mUids) { + if (range.contains(uid)) { + return true; + } + } + return false; + } + + /** + * Tests if the set of UIDs that this network applies to is the same as the passed network. + *

+ * This test only checks whether equal range objects are in both sets. It will + * return false if the ranges are not exactly the same, even if the covered UIDs + * are for an equivalent result. + *

+ * Note that this method is not very optimized, which is fine as long as it's not used very + * often. + *

+ * nc is assumed nonnull. + * + * @hide + */ + @VisibleForTesting + public boolean equalsUids(@NonNull NetworkCapabilities nc) { + Set comparedUids = nc.mUids; + if (null == comparedUids) return null == mUids; + if (null == mUids) return false; + // Make a copy so it can be mutated to check that all ranges in mUids + // also are in uids. + final Set uids = new ArraySet<>(mUids); + for (UidRange range : comparedUids) { + if (!uids.contains(range)) { + return false; + } + uids.remove(range); + } + return uids.isEmpty(); + } + + /** + * Test whether the passed NetworkCapabilities satisfies the UIDs this capabilities require. + * + * This method is called on the NetworkCapabilities embedded in a request with the + * capabilities of an available network. It checks whether all the UIDs from this listen + * (representing the UIDs that must have access to the network) are satisfied by the UIDs + * in the passed nc (representing the UIDs that this network is available to). + *

+ * As a special exception, the UID that created the passed network (as represented by its + * mOwnerUid field) always satisfies a NetworkRequest requiring it (of LISTEN + * or REQUEST types alike), even if the network does not apply to it. That is so a VPN app + * can see its own network when it listens for it. + *

+ * nc is assumed nonnull. Else, NPE. + * @see #appliesToUid + * @hide + */ + public boolean satisfiedByUids(@NonNull NetworkCapabilities nc) { + if (null == nc.mUids || null == mUids) return true; // The network satisfies everything. + for (UidRange requiredRange : mUids) { + if (requiredRange.contains(nc.mOwnerUid)) return true; + if (!nc.appliesToUidRange(requiredRange)) { + return false; + } + } + return true; + } + + /** + * Returns whether this network applies to the passed ranges. + * This assumes that to apply, the passed range has to be entirely contained + * within one of the ranges this network applies to. If the ranges are not normalized, + * this method may return false even though all required UIDs are covered because no + * single range contained them all. + * @hide + */ + @VisibleForTesting + public boolean appliesToUidRange(@Nullable UidRange requiredRange) { + if (null == mUids) return true; + for (UidRange uidRange : mUids) { + if (uidRange.containsRange(requiredRange)) { + return true; + } + } + return false; + } + + /** + * Combine the UIDs this network currently applies to with the UIDs the passed + * NetworkCapabilities apply to. + * nc is assumed nonnull. + */ + private void combineUids(@NonNull NetworkCapabilities nc) { + if (null == nc.mUids || null == mUids) { + mUids = null; + return; + } + mUids.addAll(nc.mUids); + } + + + /** + * The SSID of the network, or null if not applicable or unknown. + *

+ * This is filled in by wifi code. + * @hide + */ + private String mSSID; + + /** + * Sets the SSID of this network. + * @hide + */ + public @NonNull NetworkCapabilities setSSID(@Nullable String ssid) { + mSSID = ssid; + return this; + } + + /** + * Gets the SSID of this network, or null if none or unknown. + * @hide + */ + @SystemApi + public @Nullable String getSsid() { + return mSSID; + } + + /** + * Tests if the SSID of this network is the same as the SSID of the passed network. + * @hide + */ + public boolean equalsSSID(@NonNull NetworkCapabilities nc) { + return Objects.equals(mSSID, nc.mSSID); + } + + /** + * Check if the SSID requirements of this object are matched by the passed object. + * @hide + */ + public boolean satisfiedBySSID(@NonNull NetworkCapabilities nc) { + return mSSID == null || mSSID.equals(nc.mSSID); + } + + /** + * Combine SSIDs of the capabilities. + *

+ * This is only legal if either the SSID of this object is null, or both SSIDs are + * equal. + * @hide + */ + private void combineSSIDs(@NonNull NetworkCapabilities nc) { + if (mSSID != null && !mSSID.equals(nc.mSSID)) { + throw new IllegalStateException("Can't combine two SSIDs"); + } + setSSID(nc.mSSID); + } + + /** + * Combine a set of Capabilities to this one. Useful for coming up with the complete set. + *

+ * Note that this method may break an invariant of having a particular capability in either + * wanted or unwanted lists but never in both. Requests that have the same capability in + * both lists will never be satisfied. + * @hide + */ + public void combineCapabilities(@NonNull NetworkCapabilities nc) { + combineNetCapabilities(nc); + combineTransportTypes(nc); + combineLinkBandwidths(nc); + combineSpecifiers(nc); + combineTransportInfos(nc); + combineSignalStrength(nc); + combineUids(nc); + combineSSIDs(nc); + combineRequestor(nc); + combineAdministratorUids(nc); + } + + /** + * Check if our requirements are satisfied by the given {@code NetworkCapabilities}. + * + * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements. + * @param onlyImmutable if {@code true}, do not consider mutable requirements such as link + * bandwidth, signal strength, or validation / captive portal status. + * + * @hide + */ + private boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc, boolean onlyImmutable) { + return (nc != null + && satisfiedByNetCapabilities(nc, onlyImmutable) + && satisfiedByTransportTypes(nc) + && (onlyImmutable || satisfiedByLinkBandwidths(nc)) + && satisfiedBySpecifier(nc) + && (onlyImmutable || satisfiedBySignalStrength(nc)) + && (onlyImmutable || satisfiedByUids(nc)) + && (onlyImmutable || satisfiedBySSID(nc))) + && (onlyImmutable || satisfiedByRequestor(nc)); + } + + /** + * Check if our requirements are satisfied by the given {@code NetworkCapabilities}. + * + * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements. + * + * @hide + */ + @SystemApi + public boolean satisfiedByNetworkCapabilities(@Nullable NetworkCapabilities nc) { + return satisfiedByNetworkCapabilities(nc, false); + } + + /** + * Check if our immutable requirements are satisfied by the given {@code NetworkCapabilities}. + * + * @param nc the {@code NetworkCapabilities} that may or may not satisfy our requirements. + * + * @hide + */ + public boolean satisfiedByImmutableNetworkCapabilities(@Nullable NetworkCapabilities nc) { + return satisfiedByNetworkCapabilities(nc, true); + } + + /** + * Checks that our immutable capabilities are the same as those of the given + * {@code NetworkCapabilities} and return a String describing any difference. + * The returned String is empty if there is no difference. + * + * @hide + */ + public String describeImmutableDifferences(@Nullable NetworkCapabilities that) { + if (that == null) { + return "other NetworkCapabilities was null"; + } + + StringJoiner joiner = new StringJoiner(", "); + + // Ignore NOT_METERED being added or removed as it is effectively dynamic. http://b/63326103 + // TODO: properly support NOT_METERED as a mutable and requestable capability. + final long mask = ~MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_NOT_METERED); + long oldImmutableCapabilities = this.mNetworkCapabilities & mask; + long newImmutableCapabilities = that.mNetworkCapabilities & mask; + if (oldImmutableCapabilities != newImmutableCapabilities) { + String before = capabilityNamesOf(BitUtils.unpackBits(oldImmutableCapabilities)); + String after = capabilityNamesOf(BitUtils.unpackBits(newImmutableCapabilities)); + joiner.add(String.format("immutable capabilities changed: %s -> %s", before, after)); + } + + if (!equalsSpecifier(that)) { + NetworkSpecifier before = this.getNetworkSpecifier(); + NetworkSpecifier after = that.getNetworkSpecifier(); + joiner.add(String.format("specifier changed: %s -> %s", before, after)); + } + + if (!equalsTransportTypes(that)) { + String before = transportNamesOf(this.getTransportTypes()); + String after = transportNamesOf(that.getTransportTypes()); + joiner.add(String.format("transports changed: %s -> %s", before, after)); + } + + return joiner.toString(); + } + + /** + * Checks that our requestable capabilities are the same as those of the given + * {@code NetworkCapabilities}. + * + * @hide + */ + public boolean equalRequestableCapabilities(@Nullable NetworkCapabilities nc) { + if (nc == null) return false; + return (equalsNetCapabilitiesRequestable(nc) + && equalsTransportTypes(nc) + && equalsSpecifier(nc)); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null || (obj instanceof NetworkCapabilities == false)) return false; + NetworkCapabilities that = (NetworkCapabilities) obj; + return equalsNetCapabilities(that) + && equalsTransportTypes(that) + && equalsLinkBandwidths(that) + && equalsSignalStrength(that) + && equalsSpecifier(that) + && equalsTransportInfo(that) + && equalsUids(that) + && equalsSSID(that) + && equalsOwnerUid(that) + && equalsPrivateDnsBroken(that) + && equalsRequestor(that) + && equalsAdministratorUids(that); + } + + @Override + public int hashCode() { + return (int) (mNetworkCapabilities & 0xFFFFFFFF) + + ((int) (mNetworkCapabilities >> 32) * 3) + + ((int) (mUnwantedNetworkCapabilities & 0xFFFFFFFF) * 5) + + ((int) (mUnwantedNetworkCapabilities >> 32) * 7) + + ((int) (mTransportTypes & 0xFFFFFFFF) * 11) + + ((int) (mTransportTypes >> 32) * 13) + + mLinkUpBandwidthKbps * 17 + + mLinkDownBandwidthKbps * 19 + + Objects.hashCode(mNetworkSpecifier) * 23 + + mSignalStrength * 29 + + mOwnerUid * 31 + + Objects.hashCode(mUids) * 37 + + Objects.hashCode(mSSID) * 41 + + Objects.hashCode(mTransportInfo) * 43 + + Objects.hashCode(mPrivateDnsBroken) * 47 + + Objects.hashCode(mRequestorUid) * 53 + + Objects.hashCode(mRequestorPackageName) * 59 + + Arrays.hashCode(mAdministratorUids) * 61; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mNetworkCapabilities); + dest.writeLong(mUnwantedNetworkCapabilities); + dest.writeLong(mTransportTypes); + dest.writeInt(mLinkUpBandwidthKbps); + dest.writeInt(mLinkDownBandwidthKbps); + dest.writeParcelable((Parcelable) mNetworkSpecifier, flags); + dest.writeParcelable((Parcelable) mTransportInfo, flags); + dest.writeInt(mSignalStrength); + dest.writeArraySet(mUids); + dest.writeString(mSSID); + dest.writeBoolean(mPrivateDnsBroken); + dest.writeIntArray(getAdministratorUids()); + dest.writeInt(mOwnerUid); + dest.writeInt(mRequestorUid); + dest.writeString(mRequestorPackageName); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + @Override + public NetworkCapabilities createFromParcel(Parcel in) { + NetworkCapabilities netCap = new NetworkCapabilities(); + + netCap.mNetworkCapabilities = in.readLong(); + netCap.mUnwantedNetworkCapabilities = in.readLong(); + netCap.mTransportTypes = in.readLong(); + netCap.mLinkUpBandwidthKbps = in.readInt(); + netCap.mLinkDownBandwidthKbps = in.readInt(); + netCap.mNetworkSpecifier = in.readParcelable(null); + netCap.mTransportInfo = in.readParcelable(null); + netCap.mSignalStrength = in.readInt(); + netCap.mUids = (ArraySet) in.readArraySet( + null /* ClassLoader, null for default */); + netCap.mSSID = in.readString(); + netCap.mPrivateDnsBroken = in.readBoolean(); + netCap.setAdministratorUids(in.createIntArray()); + netCap.mOwnerUid = in.readInt(); + netCap.mRequestorUid = in.readInt(); + netCap.mRequestorPackageName = in.readString(); + return netCap; + } + @Override + public NetworkCapabilities[] newArray(int size) { + return new NetworkCapabilities[size]; + } + }; + + @Override + public @NonNull String toString() { + final StringBuilder sb = new StringBuilder("["); + if (0 != mTransportTypes) { + sb.append(" Transports: "); + appendStringRepresentationOfBitMaskToStringBuilder(sb, mTransportTypes, + NetworkCapabilities::transportNameOf, "|"); + } + if (0 != mNetworkCapabilities) { + sb.append(" Capabilities: "); + appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities, + NetworkCapabilities::capabilityNameOf, "&"); + } + if (0 != mUnwantedNetworkCapabilities) { + sb.append(" Unwanted: "); + appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities, + NetworkCapabilities::capabilityNameOf, "&"); + } + if (mLinkUpBandwidthKbps > 0) { + sb.append(" LinkUpBandwidth>=").append(mLinkUpBandwidthKbps).append("Kbps"); + } + if (mLinkDownBandwidthKbps > 0) { + sb.append(" LinkDnBandwidth>=").append(mLinkDownBandwidthKbps).append("Kbps"); + } + if (mNetworkSpecifier != null) { + sb.append(" Specifier: <").append(mNetworkSpecifier).append(">"); + } + if (mTransportInfo != null) { + sb.append(" TransportInfo: <").append(mTransportInfo).append(">"); + } + if (hasSignalStrength()) { + sb.append(" SignalStrength: ").append(mSignalStrength); + } + + if (null != mUids) { + if ((1 == mUids.size()) && (mUids.valueAt(0).count() == 1)) { + sb.append(" Uid: ").append(mUids.valueAt(0).start); + } else { + sb.append(" Uids: <").append(mUids).append(">"); + } + } + if (mOwnerUid != Process.INVALID_UID) { + sb.append(" OwnerUid: ").append(mOwnerUid); + } + + if (!ArrayUtils.isEmpty(mAdministratorUids)) { + sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids)); + } + + if (mRequestorUid != Process.INVALID_UID) { + sb.append(" RequestorUid: ").append(mRequestorUid); + } + + if (mRequestorPackageName != null) { + sb.append(" RequestorPkg: ").append(mRequestorPackageName); + } + + if (null != mSSID) { + sb.append(" SSID: ").append(mSSID); + } + + + if (mPrivateDnsBroken) { + sb.append(" PrivateDnsBroken"); + } + + sb.append("]"); + return sb.toString(); + } + + + private interface NameOf { + String nameOf(int value); + } + + /** + * @hide + */ + public static void appendStringRepresentationOfBitMaskToStringBuilder(@NonNull StringBuilder sb, + long bitMask, @NonNull NameOf nameFetcher, @NonNull String separator) { + int bitPos = 0; + boolean firstElementAdded = false; + while (bitMask != 0) { + if ((bitMask & 1) != 0) { + if (firstElementAdded) { + sb.append(separator); + } else { + firstElementAdded = true; + } + sb.append(nameFetcher.nameOf(bitPos)); + } + bitMask >>= 1; + ++bitPos; + } + } + + /** @hide */ + public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + for (int transport : getTransportTypes()) { + proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport); + } + + for (int capability : getCapabilities()) { + proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability); + } + + proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps); + proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps); + + if (mNetworkSpecifier != null) { + proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString()); + } + if (mTransportInfo != null) { + // TODO b/120653863: write transport-specific info to proto? + } + + proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength()); + proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength); + + proto.end(token); + } + + /** + * @hide + */ + public static @NonNull String capabilityNamesOf(@Nullable @NetCapability int[] capabilities) { + StringJoiner joiner = new StringJoiner("|"); + if (capabilities != null) { + for (int c : capabilities) { + joiner.add(capabilityNameOf(c)); + } + } + return joiner.toString(); + } + + /** + * @hide + */ + public static @NonNull String capabilityNameOf(@NetCapability int capability) { + switch (capability) { + case NET_CAPABILITY_MMS: return "MMS"; + case NET_CAPABILITY_SUPL: return "SUPL"; + case NET_CAPABILITY_DUN: return "DUN"; + case NET_CAPABILITY_FOTA: return "FOTA"; + case NET_CAPABILITY_IMS: return "IMS"; + case NET_CAPABILITY_CBS: return "CBS"; + case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P"; + case NET_CAPABILITY_IA: return "IA"; + case NET_CAPABILITY_RCS: return "RCS"; + case NET_CAPABILITY_XCAP: return "XCAP"; + case NET_CAPABILITY_EIMS: return "EIMS"; + case NET_CAPABILITY_NOT_METERED: return "NOT_METERED"; + case NET_CAPABILITY_INTERNET: return "INTERNET"; + case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED"; + case NET_CAPABILITY_TRUSTED: return "TRUSTED"; + case NET_CAPABILITY_NOT_VPN: return "NOT_VPN"; + case NET_CAPABILITY_VALIDATED: return "VALIDATED"; + case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL"; + case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING"; + case NET_CAPABILITY_FOREGROUND: return "FOREGROUND"; + case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED"; + case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED"; + case NET_CAPABILITY_OEM_PAID: return "OEM_PAID"; + case NET_CAPABILITY_MCX: return "MCX"; + case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY"; + case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; + case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE"; + case NET_CAPABILITY_VEHICLE_INTERNAL: return "NET_CAPABILITY_VEHICLE_INTERNAL"; + case NET_CAPABILITY_NOT_VCN_MANAGED: return "NOT_VCN_MANAGED"; + default: return Integer.toString(capability); + } + } + + /** + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static @NonNull String transportNamesOf(@Nullable @Transport int[] types) { + StringJoiner joiner = new StringJoiner("|"); + if (types != null) { + for (int t : types) { + joiner.add(transportNameOf(t)); + } + } + return joiner.toString(); + } + + /** + * @hide + */ + public static @NonNull String transportNameOf(@Transport int transport) { + if (!isValidTransport(transport)) { + return "UNKNOWN"; + } + return TRANSPORT_NAMES[transport]; + } + + private static void checkValidTransportType(@Transport int transport) { + Preconditions.checkArgument( + isValidTransport(transport), "Invalid TransportType " + transport); + } + + private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) { + return capability >= MIN_NET_CAPABILITY && capability <= MAX_NET_CAPABILITY; + } + + private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) { + Preconditions.checkArgument(isValidCapability(capability), + "NetworkCapability " + capability + "out of range"); + } + + /** + * Check if this {@code NetworkCapability} instance is metered. + * + * @return {@code true} if {@code NET_CAPABILITY_NOT_METERED} is not set on this instance. + * @hide + */ + public boolean isMetered() { + return !hasCapability(NET_CAPABILITY_NOT_METERED); + } + + /** + * Check if private dns is broken. + * + * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken. + * @hide + */ + public boolean isPrivateDnsBroken() { + return mPrivateDnsBroken; + } + + /** + * Set mPrivateDnsBroken to true when private dns is broken. + * + * @param broken the status of private DNS to be set. + * @hide + */ + public void setPrivateDnsBroken(boolean broken) { + mPrivateDnsBroken = broken; + } + + private boolean equalsPrivateDnsBroken(NetworkCapabilities nc) { + return mPrivateDnsBroken == nc.mPrivateDnsBroken; + } + + /** + * Set the UID of the app making the request. + * + * For instances of NetworkCapabilities representing a request, sets the + * UID of the app making the request. For a network created by the system, + * sets the UID of the only app whose requests can match this network. + * This can be set to {@link Process#INVALID_UID} if there is no such app, + * or if this instance of NetworkCapabilities is about to be sent to a + * party that should not learn about this. + * + * @param uid UID of the app. + * @hide + */ + public @NonNull NetworkCapabilities setRequestorUid(int uid) { + mRequestorUid = uid; + return this; + } + + /** + * Returns the UID of the app making the request. + * + * For a NetworkRequest being made by an app, contains the app's UID. For a network + * created by the system, contains the UID of the only app whose requests can match + * this network, or {@link Process#INVALID_UID} if none or if the + * caller does not have permission to learn about this. + * + * @return the uid of the app making the request. + * @hide + */ + public int getRequestorUid() { + return mRequestorUid; + } + + /** + * Set the package name of the app making the request. + * + * For instances of NetworkCapabilities representing a request, sets the + * package name of the app making the request. For a network created by the system, + * sets the package name of the only app whose requests can match this network. + * This can be set to null if there is no such app, or if this instance of + * NetworkCapabilities is about to be sent to a party that should not learn about this. + * + * @param packageName package name of the app. + * @hide + */ + public @NonNull NetworkCapabilities setRequestorPackageName(@NonNull String packageName) { + mRequestorPackageName = packageName; + return this; + } + + /** + * Returns the package name of the app making the request. + * + * For a NetworkRequest being made by an app, contains the app's package name. For a + * network created by the system, contains the package name of the only app whose + * requests can match this network, or null if none or if the caller does not have + * permission to learn about this. + * + * @return the package name of the app making the request. + * @hide + */ + @Nullable + public String getRequestorPackageName() { + return mRequestorPackageName; + } + + /** + * Set the uid and package name of the app causing this network to exist. + * + * {@see #setRequestorUid} and {@link #setRequestorPackageName} + * + * @param uid UID of the app. + * @param packageName package name of the app. + * @hide + */ + public @NonNull NetworkCapabilities setRequestorUidAndPackageName( + int uid, @NonNull String packageName) { + return setRequestorUid(uid).setRequestorPackageName(packageName); + } + + /** + * Test whether the passed NetworkCapabilities satisfies the requestor restrictions of this + * capabilities. + * + * This method is called on the NetworkCapabilities embedded in a request with the + * capabilities of an available network. If the available network, sets a specific + * requestor (by uid and optionally package name), then this will only match a request from the + * same app. If either of the capabilities have an unset uid or package name, then it matches + * everything. + *

+ * nc is assumed nonnull. Else, NPE. + */ + private boolean satisfiedByRequestor(NetworkCapabilities nc) { + // No uid set, matches everything. + if (mRequestorUid == Process.INVALID_UID || nc.mRequestorUid == Process.INVALID_UID) { + return true; + } + // uids don't match. + if (mRequestorUid != nc.mRequestorUid) return false; + // No package names set, matches everything + if (null == nc.mRequestorPackageName || null == mRequestorPackageName) return true; + // check for package name match. + return TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); + } + + /** + * Combine requestor info of the capabilities. + *

+ * This is only legal if either the requestor info of this object is reset, or both info are + * equal. + * nc is assumed nonnull. + */ + private void combineRequestor(@NonNull NetworkCapabilities nc) { + if (mRequestorUid != Process.INVALID_UID && mRequestorUid != nc.mOwnerUid) { + throw new IllegalStateException("Can't combine two uids"); + } + if (mRequestorPackageName != null + && !mRequestorPackageName.equals(nc.mRequestorPackageName)) { + throw new IllegalStateException("Can't combine two package names"); + } + setRequestorUid(nc.mRequestorUid); + setRequestorPackageName(nc.mRequestorPackageName); + } + + private boolean equalsRequestor(NetworkCapabilities nc) { + return mRequestorUid == nc.mRequestorUid + && TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); + } + + /** + * Builder class for NetworkCapabilities. + * + * This class is mainly for for {@link NetworkAgent} instances to use. Many fields in + * the built class require holding a signature permission to use - mostly + * {@link android.Manifest.permission.NETWORK_FACTORY}, but refer to the specific + * description of each setter. As this class lives entirely in app space it does not + * enforce these restrictions itself but the system server clears out the relevant + * fields when receiving a NetworkCapabilities object from a caller without the + * appropriate permission. + * + * Apps don't use this builder directly. Instead, they use {@link NetworkRequest} via + * its builder object. + * + * @hide + */ + @SystemApi + public static final class Builder { + private final NetworkCapabilities mCaps; + + /** + * Creates a new Builder to construct NetworkCapabilities objects. + */ + public Builder() { + mCaps = new NetworkCapabilities(); + } + + /** + * Creates a new Builder of NetworkCapabilities from an existing instance. + */ + public Builder(@NonNull final NetworkCapabilities nc) { + Objects.requireNonNull(nc); + mCaps = new NetworkCapabilities(nc); + } + + /** + * Adds the given transport type. + * + * Multiple transports may be added. Note that when searching for a network to satisfy a + * request, satisfying any of the transports listed in the request will satisfy the request. + * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a + * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network + * to be selected. This is logically different than + * {@code NetworkCapabilities.NET_CAPABILITY_*}. + * + * @param transportType the transport type to be added or removed. + * @return this builder + */ + @NonNull + public Builder addTransportType(@Transport int transportType) { + checkValidTransportType(transportType); + mCaps.addTransportType(transportType); + return this; + } + + /** + * Removes the given transport type. + * + * {@see #addTransportType}. + * + * @param transportType the transport type to be added or removed. + * @return this builder + */ + @NonNull + public Builder removeTransportType(@Transport int transportType) { + checkValidTransportType(transportType); + mCaps.removeTransportType(transportType); + return this; + } + + /** + * Adds the given capability. + * + * @param capability the capability + * @return this builder + */ + @NonNull + public Builder addCapability(@NetCapability final int capability) { + mCaps.setCapability(capability, true); + return this; + } + + /** + * Removes the given capability. + * + * @param capability the capability + * @return this builder + */ + @NonNull + public Builder removeCapability(@NetCapability final int capability) { + mCaps.setCapability(capability, false); + return this; + } + + /** + * Sets the owner UID. + * + * The default value is {@link Process#INVALID_UID}. Pass this value to reset. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source. + * + * @param ownerUid the owner UID + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setOwnerUid(final int ownerUid) { + mCaps.setOwnerUid(ownerUid); + return this; + } + + /** + * Sets the list of UIDs that are administrators of this network. + * + *

UIDs included in administratorUids gain administrator privileges over this + * Network. Examples of UIDs that should be included in administratorUids are: + *

    + *
  • Carrier apps with privileges for the relevant subscription + *
  • Active VPN apps + *
  • Other application groups with a particular Network-related role + *
+ * + *

In general, user-supplied networks (such as WiFi networks) do not have + * administrators. + * + *

An app is granted owner privileges over Networks that it supplies. The owner + * UID MUST always be included in administratorUids. + * + * The default value is the empty array. Pass an empty array to reset. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source, such as an app using reflection to call this or + * mutate the member in the built object. + * + * @param administratorUids the UIDs to be set as administrators of this Network. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setAdministratorUids(@NonNull final int[] administratorUids) { + Objects.requireNonNull(administratorUids); + mCaps.setAdministratorUids(administratorUids); + return this; + } + + /** + * Sets the upstream bandwidth of the link. + * + * Sets the upstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + *

+ * Note that when used to request a network, this specifies the minimum acceptable. + * When received as the state of an existing network this specifies the typical + * first hop bandwidth expected. This is never measured, but rather is inferred + * from technology type and other link parameters. It could be used to differentiate + * between very slow 1xRTT cellular links and other faster networks or even between + * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between + * fast backhauls and slow backhauls. + * + * @param upKbps the estimated first hop upstream (device to network) bandwidth. + * @return this builder + */ + @NonNull + public Builder setLinkUpstreamBandwidthKbps(final int upKbps) { + mCaps.setLinkUpstreamBandwidthKbps(upKbps); + return this; + } + + /** + * Sets the downstream bandwidth for this network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + *

+ * Note that when used to request a network, this specifies the minimum acceptable. + * When received as the state of an existing network this specifies the typical + * first hop bandwidth expected. This is never measured, but rather is inferred + * from technology type and other link parameters. It could be used to differentiate + * between very slow 1xRTT cellular links and other faster networks or even between + * 802.11b vs 802.11AC wifi technologies. It should not be used to differentiate between + * fast backhauls and slow backhauls. + * + * @param downKbps the estimated first hop downstream (network to device) bandwidth. + * @return this builder + */ + @NonNull + public Builder setLinkDownstreamBandwidthKbps(final int downKbps) { + mCaps.setLinkDownstreamBandwidthKbps(downKbps); + return this; + } + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + *

+ * + * @param specifier a concrete, parcelable framework class that extends NetworkSpecifier, + * or null to clear it. + * @return this builder + */ + @NonNull + public Builder setNetworkSpecifier(@Nullable final NetworkSpecifier specifier) { + mCaps.setNetworkSpecifier(specifier); + return this; + } + + /** + * Sets the optional transport specific information. + * + * @param info A concrete, parcelable framework class that extends {@link TransportInfo}, + * or null to clear it. + * @return this builder + */ + @NonNull + public Builder setTransportInfo(@Nullable final TransportInfo info) { + mCaps.setTransportInfo(info); + return this; + } + + /** + * Sets the signal strength. This is a signed integer, with higher values indicating a + * stronger signal. The exact units are bearer-dependent. For example, Wi-Fi uses the + * same RSSI units reported by wifi code. + *

+ * Note that when used to register a network callback, this specifies the minimum + * acceptable signal strength. When received as the state of an existing network it + * specifies the current value. A value of code SIGNAL_STRENGTH_UNSPECIFIED} means + * no value when received and has no effect when requesting a callback. + * + * Note: for security the system will throw if it receives a NetworkRequest where + * the underlying NetworkCapabilities has this member set from a source that does + * not hold the {@link android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP} + * permission. Apps with this permission can use this indirectly through + * {@link android.net.NetworkRequest}. + * + * @param signalStrength the bearer-specific signal strength. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) + public Builder setSignalStrength(final int signalStrength) { + mCaps.setSignalStrength(signalStrength); + return this; + } + + /** + * Sets the SSID of this network. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source, like an app using reflection to set this. + * + * @param ssid the SSID, or null to clear it. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setSsid(@Nullable final String ssid) { + mCaps.setSSID(ssid); + return this; + } + + /** + * Set the uid of the app causing this network to exist. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source. + * + * @param uid UID of the app. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setRequestorUid(final int uid) { + mCaps.setRequestorUid(uid); + return this; + } + + /** + * Set the package name of the app causing this network to exist. + * + * Note: for security the system will clear out this field when received from a + * non-privileged source. + * + * @param packageName package name of the app, or null to clear it. + * @return this builder + */ + @NonNull + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setRequestorPackageName(@Nullable final String packageName) { + mCaps.setRequestorPackageName(packageName); + return this; + } + + /** + * Builds the instance of the capabilities. + * + * @return the built instance of NetworkCapabilities. + */ + @NonNull + public NetworkCapabilities build() { + if (mCaps.getOwnerUid() != Process.INVALID_UID) { + if (!ArrayUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) { + throw new IllegalStateException("The owner UID must be included in " + + " administrator UIDs."); + } + } + return new NetworkCapabilities(mCaps); + } + } +} diff --git a/framework/src/android/net/NetworkConfig.java b/framework/src/android/net/NetworkConfig.java new file mode 100644 index 0000000000..32a2cda003 --- /dev/null +++ b/framework/src/android/net/NetworkConfig.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010 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; + +import java.util.Locale; + +/** + * Describes the buildtime configuration of a network. + * Holds settings read from resources. + * @hide + */ +public class NetworkConfig { + /** + * Human readable string + */ + public String name; + + /** + * Type from ConnectivityManager + */ + public int type; + + /** + * the radio number from radio attributes config + */ + public int radio; + + /** + * higher number == higher priority when turning off connections + */ + public int priority; + + /** + * indicates the boot time dependencyMet setting + */ + public boolean dependencyMet; + + /** + * indicates the default restoral timer in seconds + * if the network is used as a special network feature + * -1 indicates no restoration of default + */ + public int restoreTime; + + /** + * input string from config.xml resource. Uses the form: + * [Connection name],[ConnectivityManager connection type], + * [associated radio-type],[priority],[dependencyMet] + */ + public NetworkConfig(String init) { + String fragments[] = init.split(","); + name = fragments[0].trim().toLowerCase(Locale.ROOT); + type = Integer.parseInt(fragments[1]); + radio = Integer.parseInt(fragments[2]); + priority = Integer.parseInt(fragments[3]); + restoreTime = Integer.parseInt(fragments[4]); + dependencyMet = Boolean.parseBoolean(fragments[5]); + } + + /** + * Indicates if this network is supposed to be default-routable + */ + public boolean isDefault() { + return (type == radio); + } +} diff --git a/framework/src/android/net/NetworkInfo.aidl b/framework/src/android/net/NetworkInfo.aidl new file mode 100644 index 0000000000..f501873029 --- /dev/null +++ b/framework/src/android/net/NetworkInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2007, 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; + +parcelable NetworkInfo; diff --git a/framework/src/android/net/NetworkInfo.java b/framework/src/android/net/NetworkInfo.java new file mode 100644 index 0000000000..d752901e2e --- /dev/null +++ b/framework/src/android/net/NetworkInfo.java @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2008 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Annotation.NetworkType; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.EnumMap; + +/** + * Describes the status of a network interface. + *

Use {@link ConnectivityManager#getActiveNetworkInfo()} to get an instance that represents + * the current network connection. + * + * @deprecated Callers should instead use the {@link ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes, or switch to use + * {@link ConnectivityManager#getNetworkCapabilities} or + * {@link ConnectivityManager#getLinkProperties} to get information synchronously. Keep + * in mind that while callbacks are guaranteed to be called for every event in order, + * synchronous calls have no such constraints, and as such it is unadvisable to use the + * synchronous methods inside the callbacks as they will often not offer a view of + * networking that is consistent (that is: they may return a past or a future state with + * respect to the event being processed by the callback). Instead, callers are advised + * to only use the arguments of the callbacks, possibly memorizing the specific bits of + * information they need to keep from one callback to another. + */ +@Deprecated +public class NetworkInfo implements Parcelable { + + /** + * Coarse-grained network state. This is probably what most applications should + * use, rather than {@link android.net.NetworkInfo.DetailedState DetailedState}. + * The mapping between the two is as follows: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Detailed stateCoarse-grained state
IDLEDISCONNECTED
SCANNINGDISCONNECTED
CONNECTINGCONNECTING
AUTHENTICATINGCONNECTING
OBTAINING_IPADDRCONNECTING
VERIFYING_POOR_LINKCONNECTING
CAPTIVE_PORTAL_CHECKCONNECTING
CONNECTEDCONNECTED
SUSPENDEDSUSPENDED
DISCONNECTINGDISCONNECTING
DISCONNECTEDDISCONNECTED
FAILEDDISCONNECTED
BLOCKEDDISCONNECTED
+ * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public enum State { + CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN + } + + /** + * The fine-grained state of a network connection. This level of detail + * is probably of interest to few applications. Most should use + * {@link android.net.NetworkInfo.State State} instead. + * + * @deprecated See {@link NetworkInfo}. + */ + @Deprecated + public enum DetailedState { + /** Ready to start data connection setup. */ + IDLE, + /** Searching for an available access point. */ + SCANNING, + /** Currently setting up data connection. */ + CONNECTING, + /** Network link established, performing authentication. */ + AUTHENTICATING, + /** Awaiting response from DHCP server in order to assign IP address information. */ + OBTAINING_IPADDR, + /** IP traffic should be available. */ + CONNECTED, + /** IP traffic is suspended */ + SUSPENDED, + /** Currently tearing down data connection. */ + DISCONNECTING, + /** IP traffic not available. */ + DISCONNECTED, + /** Attempt to connect failed. */ + FAILED, + /** Access to this network is blocked. */ + BLOCKED, + /** Link has poor connectivity. */ + VERIFYING_POOR_LINK, + /** Checking if network is a captive portal */ + CAPTIVE_PORTAL_CHECK + } + + /** + * This is the map described in the Javadoc comment above. The positions + * of the elements of the array must correspond to the ordinal values + * of DetailedState. + */ + private static final EnumMap stateMap = + new EnumMap(DetailedState.class); + + static { + stateMap.put(DetailedState.IDLE, State.DISCONNECTED); + stateMap.put(DetailedState.SCANNING, State.DISCONNECTED); + stateMap.put(DetailedState.CONNECTING, State.CONNECTING); + stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING); + stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING); + stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING); + stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING); + stateMap.put(DetailedState.CONNECTED, State.CONNECTED); + stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED); + stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING); + stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED); + stateMap.put(DetailedState.FAILED, State.DISCONNECTED); + stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED); + } + + private int mNetworkType; + private int mSubtype; + private String mTypeName; + private String mSubtypeName; + @NonNull + private State mState; + @NonNull + private DetailedState mDetailedState; + private String mReason; + private String mExtraInfo; + private boolean mIsFailover; + private boolean mIsAvailable; + private boolean mIsRoaming; + + /** + * Create a new instance of NetworkInfo. + * + * This may be useful for apps to write unit tests. + * + * @param type the legacy type of the network, as one of the ConnectivityManager.TYPE_* + * constants. + * @param subtype the subtype if applicable, as one of the TelephonyManager.NETWORK_TYPE_* + * constants. + * @param typeName a human-readable string for the network type, or an empty string or null. + * @param subtypeName a human-readable string for the subtype, or an empty string or null. + */ + public NetworkInfo(int type, @NetworkType int subtype, + @Nullable String typeName, @Nullable String subtypeName) { + if (!ConnectivityManager.isNetworkTypeValid(type) + && type != ConnectivityManager.TYPE_NONE) { + throw new IllegalArgumentException("Invalid network type: " + type); + } + mNetworkType = type; + mSubtype = subtype; + mTypeName = typeName; + mSubtypeName = subtypeName; + setDetailedState(DetailedState.IDLE, null, null); + mState = State.UNKNOWN; + } + + /** {@hide} */ + @UnsupportedAppUsage + public NetworkInfo(NetworkInfo source) { + if (source != null) { + synchronized (source) { + mNetworkType = source.mNetworkType; + mSubtype = source.mSubtype; + mTypeName = source.mTypeName; + mSubtypeName = source.mSubtypeName; + mState = source.mState; + mDetailedState = source.mDetailedState; + mReason = source.mReason; + mExtraInfo = source.mExtraInfo; + mIsFailover = source.mIsFailover; + mIsAvailable = source.mIsAvailable; + mIsRoaming = source.mIsRoaming; + } + } + } + + /** + * Reports the type of network to which the + * info in this {@code NetworkInfo} pertains. + * @return one of {@link ConnectivityManager#TYPE_MOBILE}, {@link + * ConnectivityManager#TYPE_WIFI}, {@link ConnectivityManager#TYPE_WIMAX}, {@link + * ConnectivityManager#TYPE_ETHERNET}, {@link ConnectivityManager#TYPE_BLUETOOTH}, or other + * types defined by {@link ConnectivityManager}. + * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport} + * instead with one of the NetworkCapabilities#TRANSPORT_* constants : + * {@link #getType} and {@link #getTypeName} cannot account for networks using + * multiple transports. Note that generally apps should not care about transport; + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and + * {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that + * apps concerned with meteredness or bandwidth should be looking at, as they + * offer this information with much better accuracy. + */ + @Deprecated + public int getType() { + synchronized (this) { + return mNetworkType; + } + } + + /** + * @deprecated Use {@link NetworkCapabilities} instead + * @hide + */ + @Deprecated + public void setType(int type) { + synchronized (this) { + mNetworkType = type; + } + } + + /** + * Return a network-type-specific integer describing the subtype + * of the network. + * @return the network subtype + * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead. + */ + @Deprecated + public int getSubtype() { + synchronized (this) { + return mSubtype; + } + } + + /** + * @hide + */ + @UnsupportedAppUsage + public void setSubtype(int subtype, String subtypeName) { + synchronized (this) { + mSubtype = subtype; + mSubtypeName = subtypeName; + } + } + + /** + * Return a human-readable name describe the type of the network, + * for example "WIFI" or "MOBILE". + * @return the name of the network type + * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport} + * instead with one of the NetworkCapabilities#TRANSPORT_* constants : + * {@link #getType} and {@link #getTypeName} cannot account for networks using + * multiple transports. Note that generally apps should not care about transport; + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and + * {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that + * apps concerned with meteredness or bandwidth should be looking at, as they + * offer this information with much better accuracy. + */ + @Deprecated + public String getTypeName() { + synchronized (this) { + return mTypeName; + } + } + + /** + * Return a human-readable name describing the subtype of the network. + * @return the name of the network subtype + * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead. + */ + @Deprecated + public String getSubtypeName() { + synchronized (this) { + return mSubtypeName; + } + } + + /** + * Indicates whether network connectivity exists or is in the process + * of being established. This is good for applications that need to + * do anything related to the network other than read or write data. + * For the latter, call {@link #isConnected()} instead, which guarantees + * that the network is fully usable. + * @return {@code true} if network connectivity exists or is in the process + * of being established, {@code false} otherwise. + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public boolean isConnectedOrConnecting() { + synchronized (this) { + return mState == State.CONNECTED || mState == State.CONNECTING; + } + } + + /** + * Indicates whether network connectivity exists and it is possible to establish + * connections and pass data. + *

Always call this before attempting to perform data transactions. + * @return {@code true} if network connectivity exists, {@code false} otherwise. + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. See + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public boolean isConnected() { + synchronized (this) { + return mState == State.CONNECTED; + } + } + + /** + * Indicates whether network connectivity is possible. A network is unavailable + * when a persistent or semi-persistent condition prevents the possibility + * of connecting to that network. Examples include + *

    + *
  • The device is out of the coverage area for any network of this type.
  • + *
  • The device is on a network other than the home network (i.e., roaming), and + * data roaming has been disabled.
  • + *
  • The device's radio is turned off, e.g., because airplane mode is enabled.
  • + *
+ * Since Android L, this always returns {@code true}, because the system only + * returns info for available networks. + * @return {@code true} if the network is available, {@code false} otherwise + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public boolean isAvailable() { + synchronized (this) { + return mIsAvailable; + } + } + + /** + * Sets if the network is available, ie, if the connectivity is possible. + * @param isAvailable the new availability value. + * @deprecated Use {@link NetworkCapabilities} instead + * + * @hide + */ + @Deprecated + @UnsupportedAppUsage + public void setIsAvailable(boolean isAvailable) { + synchronized (this) { + mIsAvailable = isAvailable; + } + } + + /** + * Indicates whether the current attempt to connect to the network + * resulted from the ConnectivityManager trying to fail over to this + * network following a disconnect from another network. + * @return {@code true} if this is a failover attempt, {@code false} + * otherwise. + * @deprecated This field is not populated in recent Android releases, + * and does not make a lot of sense in a multi-network world. + */ + @Deprecated + public boolean isFailover() { + synchronized (this) { + return mIsFailover; + } + } + + /** + * Set the failover boolean. + * @param isFailover {@code true} to mark the current connection attempt + * as a failover. + * @deprecated This hasn't been set in any recent Android release. + * @hide + */ + @Deprecated + @UnsupportedAppUsage + public void setFailover(boolean isFailover) { + synchronized (this) { + mIsFailover = isFailover; + } + } + + /** + * Indicates whether the device is currently roaming on this network. When + * {@code true}, it suggests that use of data on this network may incur + * extra costs. + * + * @return {@code true} if roaming is in effect, {@code false} otherwise. + * @deprecated Callers should switch to checking + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} + * instead, since that handles more complex situations, such as + * VPNs. + */ + @Deprecated + public boolean isRoaming() { + synchronized (this) { + return mIsRoaming; + } + } + + /** + * @deprecated Use {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} instead. + * {@hide} + */ + @VisibleForTesting + @Deprecated + @UnsupportedAppUsage + public void setRoaming(boolean isRoaming) { + synchronized (this) { + mIsRoaming = isRoaming; + } + } + + /** + * Reports the current coarse-grained state of the network. + * @return the coarse-grained state + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public State getState() { + synchronized (this) { + return mState; + } + } + + /** + * Reports the current fine-grained state of the network. + * @return the fine-grained state + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. See + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. + */ + @Deprecated + public @NonNull DetailedState getDetailedState() { + synchronized (this) { + return mDetailedState; + } + } + + /** + * Sets the fine-grained state of the network. + * + * This is only useful for testing. + * + * @param detailedState the {@link DetailedState}. + * @param reason a {@code String} indicating the reason for the state change, + * if one was supplied. May be {@code null}. + * @param extraInfo an optional {@code String} providing addditional network state + * information passed up from the lower networking layers. + * @deprecated Use {@link NetworkCapabilities} instead. + */ + @Deprecated + public void setDetailedState(@NonNull DetailedState detailedState, @Nullable String reason, + @Nullable String extraInfo) { + synchronized (this) { + this.mDetailedState = detailedState; + this.mState = stateMap.get(detailedState); + this.mReason = reason; + this.mExtraInfo = extraInfo; + } + } + + /** + * Set the extraInfo field. + * @param extraInfo an optional {@code String} providing addditional network state + * information passed up from the lower networking layers. + * @deprecated See {@link NetworkInfo#getExtraInfo}. + * @hide + */ + @Deprecated + public void setExtraInfo(String extraInfo) { + synchronized (this) { + this.mExtraInfo = extraInfo; + } + } + + /** + * Report the reason an attempt to establish connectivity failed, + * if one is available. + * @return the reason for failure, or null if not available + * @deprecated This method does not have a consistent contract that could make it useful + * to callers. + */ + public String getReason() { + synchronized (this) { + return mReason; + } + } + + /** + * Report the extra information about the network state, if any was + * provided by the lower networking layers. + * @return the extra information, or null if not available + * @deprecated Use other services e.g. WifiManager to get additional information passed up from + * the lower networking layers. + */ + @Deprecated + public String getExtraInfo() { + synchronized (this) { + return mExtraInfo; + } + } + + @Override + public String toString() { + synchronized (this) { + final StringBuilder builder = new StringBuilder("["); + builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()). + append("], state: ").append(mState).append("/").append(mDetailedState). + append(", reason: ").append(mReason == null ? "(unspecified)" : mReason). + append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo). + append(", failover: ").append(mIsFailover). + append(", available: ").append(mIsAvailable). + append(", roaming: ").append(mIsRoaming). + append("]"); + return builder.toString(); + } + } + + /** + * Returns a brief summary string suitable for debugging. + * @hide + */ + public String toShortString() { + synchronized (this) { + final StringBuilder builder = new StringBuilder(); + builder.append(getTypeName()); + + final String subtype = getSubtypeName(); + if (!TextUtils.isEmpty(subtype)) { + builder.append("[").append(subtype).append("]"); + } + + builder.append(" "); + builder.append(mDetailedState); + if (mIsRoaming) { + builder.append(" ROAMING"); + } + if (mExtraInfo != null) { + builder.append(" extra: ").append(mExtraInfo); + } + return builder.toString(); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + synchronized (this) { + dest.writeInt(mNetworkType); + dest.writeInt(mSubtype); + dest.writeString(mTypeName); + dest.writeString(mSubtypeName); + dest.writeString(mState.name()); + dest.writeString(mDetailedState.name()); + dest.writeInt(mIsFailover ? 1 : 0); + dest.writeInt(mIsAvailable ? 1 : 0); + dest.writeInt(mIsRoaming ? 1 : 0); + dest.writeString(mReason); + dest.writeString(mExtraInfo); + } + } + + public static final @android.annotation.NonNull Creator CREATOR = new Creator() { + @Override + public NetworkInfo createFromParcel(Parcel in) { + int netType = in.readInt(); + int subtype = in.readInt(); + String typeName = in.readString(); + String subtypeName = in.readString(); + NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName); + netInfo.mState = State.valueOf(in.readString()); + netInfo.mDetailedState = DetailedState.valueOf(in.readString()); + netInfo.mIsFailover = in.readInt() != 0; + netInfo.mIsAvailable = in.readInt() != 0; + netInfo.mIsRoaming = in.readInt() != 0; + netInfo.mReason = in.readString(); + netInfo.mExtraInfo = in.readString(); + return netInfo; + } + + @Override + public NetworkInfo[] newArray(int size) { + return new NetworkInfo[size]; + } + }; +} diff --git a/framework/src/android/net/NetworkProvider.java b/framework/src/android/net/NetworkProvider.java new file mode 100644 index 0000000000..14cb51c85d --- /dev/null +++ b/framework/src/android/net/NetworkProvider.java @@ -0,0 +1,162 @@ +/* + * 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 android.net; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.util.Log; + +/** + * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device + * to networks and makes them available to the core network stack by creating + * {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted + * with via networking APIs such as {@link ConnectivityManager}. + * + * Subclasses should implement {@link #onNetworkRequested} and {@link #onNetworkRequestWithdrawn} + * to receive {@link NetworkRequest}s sent by the system and by apps. A network that is not the + * best (highest-scoring) network for any request is generally not used by the system, and torn + * down. + * + * @hide + */ +@SystemApi +public class NetworkProvider { + /** + * {@code providerId} value that indicates the absence of a provider. It is the providerId of + * any NetworkProvider that is not currently registered, and of any NetworkRequest that is not + * currently being satisfied by a network. + */ + public static final int ID_NONE = -1; + + /** + * The first providerId value that will be allocated. + * @hide only used by ConnectivityService. + */ + public static final int FIRST_PROVIDER_ID = 1; + + /** @hide only used by ConnectivityService */ + public static final int CMD_REQUEST_NETWORK = 1; + /** @hide only used by ConnectivityService */ + public static final int CMD_CANCEL_REQUEST = 2; + + private final Messenger mMessenger; + private final String mName; + private final Context mContext; + + private int mProviderId = ID_NONE; + + /** + * Constructs a new NetworkProvider. + * + * @param looper the Looper on which to run {@link #onNetworkRequested} and + * {@link #onNetworkRequestWithdrawn}. + * @param name the name of the listener, used only for debugging. + * + * @hide + */ + @SystemApi + public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) { + Handler handler = new Handler(looper) { + @Override + public void handleMessage(Message m) { + switch (m.what) { + case CMD_REQUEST_NETWORK: + onNetworkRequested((NetworkRequest) m.obj, m.arg1, m.arg2); + break; + case CMD_CANCEL_REQUEST: + onNetworkRequestWithdrawn((NetworkRequest) m.obj); + break; + default: + Log.e(mName, "Unhandled message: " + m.what); + } + } + }; + mContext = context; + mMessenger = new Messenger(handler); + mName = name; + } + + // TODO: consider adding a register() method so ConnectivityManager does not need to call this. + /** @hide */ + public @Nullable Messenger getMessenger() { + return mMessenger; + } + + /** @hide */ + public @NonNull String getName() { + return mName; + } + + /** + * Returns the ID of this provider. This is known only once the provider is registered via + * {@link ConnectivityManager#registerNetworkProvider()}, otherwise the ID is {@link #ID_NONE}. + * This ID must be used when registering any {@link NetworkAgent}s. + */ + public int getProviderId() { + return mProviderId; + } + + /** @hide */ + public void setProviderId(int providerId) { + mProviderId = providerId; + } + + /** + * Called when a NetworkRequest is received. The request may be a new request or an existing + * request with a different score. + * + * @param request the NetworkRequest being received + * @param score the score of the network currently satisfying the request, or 0 if none. + * @param providerId the ID of the provider that created the network currently satisfying this + * request, or {@link #ID_NONE} if none. + * + * @hide + */ + @SystemApi + public void onNetworkRequested(@NonNull NetworkRequest request, + @IntRange(from = 0, to = 99) int score, int providerId) {} + + /** + * Called when a NetworkRequest is withdrawn. + * @hide + */ + @SystemApi + public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {} + + /** + * Asserts that no provider will ever be able to satisfy the specified request. The provider + * must only call this method if it knows that it is the only provider on the system capable of + * satisfying this request, and that the request cannot be satisfied. The application filing the + * request will receive an {@link NetworkCallback#onUnavailable()} callback. + * + * @param request the request that permanently cannot be fulfilled + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { + ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request); + } +} diff --git a/framework/src/android/net/NetworkRequest.aidl b/framework/src/android/net/NetworkRequest.aidl new file mode 100644 index 0000000000..508defc6b4 --- /dev/null +++ b/framework/src/android/net/NetworkRequest.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014, 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; + +parcelable NetworkRequest; + diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java new file mode 100644 index 0000000000..04011fc681 --- /dev/null +++ b/framework/src/android/net/NetworkRequest.java @@ -0,0 +1,582 @@ +/* + * Copyright (C) 2014 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.net.NetworkCapabilities.NetCapability; +import android.net.NetworkCapabilities.Transport; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Process; +import android.text.TextUtils; +import android.util.proto.ProtoOutputStream; + +import java.util.Objects; +import java.util.Set; + +/** + * Defines a request for a network, made through {@link NetworkRequest.Builder} and used + * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes + * via {@link ConnectivityManager#registerNetworkCallback}. + */ +public class NetworkRequest implements Parcelable { + /** + * The first requestId value that will be allocated. + * @hide only used by ConnectivityService. + */ + public static final int FIRST_REQUEST_ID = 1; + + /** + * The requestId value that represents the absence of a request. + * @hide only used by ConnectivityService. + */ + public static final int REQUEST_ID_NONE = -1; + + /** + * The {@link NetworkCapabilities} that define this request. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public final @NonNull NetworkCapabilities networkCapabilities; + + /** + * Identifies the request. NetworkRequests should only be constructed by + * the Framework and given out to applications as tokens to be used to identify + * the request. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public final int requestId; + + /** + * Set for legacy requests and the default. Set to TYPE_NONE for none. + * Causes CONNECTIVITY_ACTION broadcasts to be sent. + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public final int legacyType; + + /** + * A NetworkRequest as used by the system can be one of the following types: + * + * - LISTEN, for which the framework will issue callbacks about any + * and all networks that match the specified NetworkCapabilities, + * + * - REQUEST, capable of causing a specific network to be created + * first (e.g. a telephony DUN request), the framework will issue + * callbacks about the single, highest scoring current network + * (if any) that matches the specified NetworkCapabilities, or + * + * - TRACK_DEFAULT, a hybrid of the two designed such that the + * framework will issue callbacks for the single, highest scoring + * current network (if any) that matches the capabilities of the + * default Internet request (mDefaultRequest), but which cannot cause + * the framework to either create or retain the existence of any + * specific network. Note that from the point of view of the request + * matching code, TRACK_DEFAULT is identical to REQUEST: its special + * behaviour is not due to different semantics, but to the fact that + * the system will only ever create a TRACK_DEFAULT with capabilities + * that are identical to the default request's capabilities, thus + * causing it to share fate in every way with the default request. + * + * - BACKGROUND_REQUEST, like REQUEST but does not cause any networks + * to retain the NET_CAPABILITY_FOREGROUND capability. A network with + * no foreground requests is in the background. A network that has + * one or more background requests and loses its last foreground + * request to a higher-scoring network will not go into the + * background immediately, but will linger and go into the background + * after the linger timeout. + * + * - The value NONE is used only by applications. When an application + * creates a NetworkRequest, it does not have a type; the type is set + * by the system depending on the method used to file the request + * (requestNetwork, registerNetworkCallback, etc.). + * + * @hide + */ + public static enum Type { + NONE, + LISTEN, + TRACK_DEFAULT, + REQUEST, + BACKGROUND_REQUEST, + }; + + /** + * The type of the request. This is only used by the system and is always NONE elsewhere. + * + * @hide + */ + public final Type type; + + /** + * @hide + */ + public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId, Type type) { + if (nc == null) { + throw new NullPointerException(); + } + requestId = rId; + networkCapabilities = nc; + this.legacyType = legacyType; + this.type = type; + } + + /** + * @hide + */ + public NetworkRequest(NetworkRequest that) { + networkCapabilities = new NetworkCapabilities(that.networkCapabilities); + requestId = that.requestId; + this.legacyType = that.legacyType; + this.type = that.type; + } + + /** + * Builder used to create {@link NetworkRequest} objects. Specify the Network features + * needed in terms of {@link NetworkCapabilities} features + */ + public static class Builder { + private final NetworkCapabilities mNetworkCapabilities; + + /** + * Default constructor for Builder. + */ + public Builder() { + // By default, restrict this request to networks available to this app. + // Apps can rescind this restriction, but ConnectivityService will enforce + // it for apps that do not have the NETWORK_SETTINGS permission. + mNetworkCapabilities = new NetworkCapabilities(); + mNetworkCapabilities.setSingleUid(Process.myUid()); + } + + /** + * Build {@link NetworkRequest} give the current set of capabilities. + */ + public NetworkRequest build() { + // Make a copy of mNetworkCapabilities so we don't inadvertently remove NOT_RESTRICTED + // when later an unrestricted capability could be added to mNetworkCapabilities, in + // which case NOT_RESTRICTED should be returned to mNetworkCapabilities, which + // maybeMarkCapabilitiesRestricted() doesn't add back. + final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities); + nc.maybeMarkCapabilitiesRestricted(); + return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE, + ConnectivityManager.REQUEST_ID_UNSET, Type.NONE); + } + + /** + * Add the given capability requirement to this builder. These represent + * the requested network's required capabilities. Note that when searching + * for a network to satisfy a request, all capabilities requested must be + * satisfied. + * + * @param capability The capability to add. + * @return The builder to facilitate chaining + * {@code builder.addCapability(...).addCapability();}. + */ + public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.addCapability(capability); + return this; + } + + /** + * Removes (if found) the given capability from this builder instance. + * + * @param capability The capability to remove. + * @return The builder to facilitate chaining. + */ + public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.removeCapability(capability); + return this; + } + + /** + * Set the {@code NetworkCapabilities} for this builder instance, + * overriding any capabilities that had been previously set. + * + * @param nc The superseding {@code NetworkCapabilities} instance. + * @return The builder to facilitate chaining. + * @hide + */ + public Builder setCapabilities(NetworkCapabilities nc) { + mNetworkCapabilities.set(nc); + return this; + } + + /** + * Set the watched UIDs for this request. This will be reset and wiped out unless + * the calling app holds the CHANGE_NETWORK_STATE permission. + * + * @param uids The watched UIDs as a set of UidRanges, or null for everything. + * @return The builder to facilitate chaining. + * @hide + */ + public Builder setUids(Set uids) { + mNetworkCapabilities.setUids(uids); + return this; + } + + /** + * Add a capability that must not exist in the requested network. + *

+ * If the capability was previously added to the list of required capabilities (for + * example, it was there by default or added using {@link #addCapability(int)} method), then + * it will be removed from the list of required capabilities as well. + * + * @see #addCapability(int) + * + * @param capability The capability to add to unwanted capability list. + * @return The builder to facilitate chaining. + * + * @hide + */ + public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.addUnwantedCapability(capability); + return this; + } + + /** + * Completely clears all the {@code NetworkCapabilities} from this builder instance, + * removing even the capabilities that are set by default when the object is constructed. + * + * @return The builder to facilitate chaining. + */ + @NonNull + public Builder clearCapabilities() { + mNetworkCapabilities.clearAll(); + return this; + } + + /** + * Adds the given transport requirement to this builder. These represent + * the set of allowed transports for the request. Only networks using one + * of these transports will satisfy the request. If no particular transports + * are required, none should be specified here. + * + * @param transportType The transport type to add. + * @return The builder to facilitate chaining. + */ + public Builder addTransportType(@NetworkCapabilities.Transport int transportType) { + mNetworkCapabilities.addTransportType(transportType); + return this; + } + + /** + * Removes (if found) the given transport from this builder instance. + * + * @param transportType The transport type to remove. + * @return The builder to facilitate chaining. + */ + public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) { + mNetworkCapabilities.removeTransportType(transportType); + return this; + } + + /** + * @hide + */ + public Builder setLinkUpstreamBandwidthKbps(int upKbps) { + mNetworkCapabilities.setLinkUpstreamBandwidthKbps(upKbps); + return this; + } + /** + * @hide + */ + public Builder setLinkDownstreamBandwidthKbps(int downKbps) { + mNetworkCapabilities.setLinkDownstreamBandwidthKbps(downKbps); + return this; + } + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + *

+ * If the {@code networkSpecifier} is provided, it shall be interpreted as follows: + *
    + *
  • If the specifier can be parsed as an integer, it will be treated as a + * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be + * interpreted as a SubscriptionId. + *
  • If the value is an ethernet interface name, it will be treated as such. + *
  • For all other cases, the behavior is undefined. + *
+ * + * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular + * network request or an ethernet interface name in ethernet + * network request. + * + * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead. + */ + @Deprecated + public Builder setNetworkSpecifier(String networkSpecifier) { + try { + int subId = Integer.parseInt(networkSpecifier); + return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(subId).build()); + } catch (NumberFormatException nfe) { + // A StringNetworkSpecifier does not accept null or empty ("") strings. When network + // specifiers were strings a null string and an empty string were considered + // equivalent. Hence no meaning is attached to a null or empty ("") string. + return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null + : new StringNetworkSpecifier(networkSpecifier)); + } + } + + /** + * Sets the optional bearer specific network specifier. + * This has no meaning if a single transport is also not specified, so calling + * this without a single transport set will generate an exception, as will + * subsequently adding or removing transports after this is set. + *

+ * + * @param networkSpecifier A concrete, parcelable framework class that extends + * NetworkSpecifier. + */ + public Builder setNetworkSpecifier(NetworkSpecifier networkSpecifier) { + if (networkSpecifier instanceof MatchAllNetworkSpecifier) { + throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted"); + } + mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); + return this; + } + + /** + * Sets the signal strength. This is a signed integer, with higher values indicating a + * stronger signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same + * RSSI units reported by WifiManager. + *

+ * Note that when used to register a network callback, this specifies the minimum acceptable + * signal strength. When received as the state of an existing network it specifies the + * current value. A value of {@code SIGNAL_STRENGTH_UNSPECIFIED} means no value when + * received and has no effect when requesting a callback. + * + *

This method requires the caller to hold the + * {@link android.Manifest.permission#NETWORK_SIGNAL_STRENGTH_WAKEUP} permission + * + * @param signalStrength the bearer-specific signal strength. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) + public @NonNull Builder setSignalStrength(int signalStrength) { + mNetworkCapabilities.setSignalStrength(signalStrength); + return this; + } + } + + // implement the Parcelable interface + public int describeContents() { + return 0; + } + public void writeToParcel(Parcel dest, int flags) { + networkCapabilities.writeToParcel(dest, flags); + dest.writeInt(legacyType); + dest.writeInt(requestId); + dest.writeString(type.name()); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public NetworkRequest createFromParcel(Parcel in) { + NetworkCapabilities nc = NetworkCapabilities.CREATOR.createFromParcel(in); + int legacyType = in.readInt(); + int requestId = in.readInt(); + Type type = Type.valueOf(in.readString()); // IllegalArgumentException if invalid. + NetworkRequest result = new NetworkRequest(nc, legacyType, requestId, type); + return result; + } + public NetworkRequest[] newArray(int size) { + return new NetworkRequest[size]; + } + }; + + /** + * Returns true iff. this NetworkRequest is of type LISTEN. + * + * @hide + */ + public boolean isListen() { + return type == Type.LISTEN; + } + + /** + * Returns true iff. the contained NetworkRequest is one that: + * + * - should be associated with at most one satisfying network + * at a time; + * + * - should cause a network to be kept up, but not necessarily in + * the foreground, if it is the best network which can satisfy the + * NetworkRequest. + * + * For full detail of how isRequest() is used for pairing Networks with + * NetworkRequests read rematchNetworkAndRequests(). + * + * @hide + */ + public boolean isRequest() { + return isForegroundRequest() || isBackgroundRequest(); + } + + /** + * Returns true iff. the contained NetworkRequest is one that: + * + * - should be associated with at most one satisfying network + * at a time; + * + * - should cause a network to be kept up and in the foreground if + * it is the best network which can satisfy the NetworkRequest. + * + * For full detail of how isRequest() is used for pairing Networks with + * NetworkRequests read rematchNetworkAndRequests(). + * + * @hide + */ + public boolean isForegroundRequest() { + return type == Type.TRACK_DEFAULT || type == Type.REQUEST; + } + + /** + * Returns true iff. this NetworkRequest is of type BACKGROUND_REQUEST. + * + * @hide + */ + public boolean isBackgroundRequest() { + return type == Type.BACKGROUND_REQUEST; + } + + /** + * @see Builder#addCapability(int) + */ + public boolean hasCapability(@NetCapability int capability) { + return networkCapabilities.hasCapability(capability); + } + + /** + * @see Builder#addUnwantedCapability(int) + * + * @hide + */ + public boolean hasUnwantedCapability(@NetCapability int capability) { + return networkCapabilities.hasUnwantedCapability(capability); + } + + /** + * Returns true if and only if the capabilities requested in this NetworkRequest are satisfied + * by the provided {@link NetworkCapabilities}. + * + * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not + * satisfy any request. + */ + public boolean canBeSatisfiedBy(@Nullable NetworkCapabilities nc) { + return networkCapabilities.satisfiedByNetworkCapabilities(nc); + } + + /** + * @see Builder#addTransportType(int) + */ + public boolean hasTransport(@Transport int transportType) { + return networkCapabilities.hasTransport(transportType); + } + + /** + * @see Builder#setNetworkSpecifier(NetworkSpecifier) + */ + @Nullable + public NetworkSpecifier getNetworkSpecifier() { + return networkCapabilities.getNetworkSpecifier(); + } + + /** + * @return the uid of the app making the request. + * + * Note: This could return {@link Process#INVALID_UID} if the {@link NetworkRequest} object was + * not obtained from {@link ConnectivityManager}. + * @hide + */ + @SystemApi + public int getRequestorUid() { + return networkCapabilities.getRequestorUid(); + } + + /** + * @return the package name of the app making the request. + * + * Note: This could return {@code null} if the {@link NetworkRequest} object was not obtained + * from {@link ConnectivityManager}. + * @hide + */ + @SystemApi + @Nullable + public String getRequestorPackageName() { + return networkCapabilities.getRequestorPackageName(); + } + + public String toString() { + return "NetworkRequest [ " + type + " id=" + requestId + + (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") + + ", " + networkCapabilities.toString() + " ]"; + } + + private int typeToProtoEnum(Type t) { + switch (t) { + case NONE: + return NetworkRequestProto.TYPE_NONE; + case LISTEN: + return NetworkRequestProto.TYPE_LISTEN; + case TRACK_DEFAULT: + return NetworkRequestProto.TYPE_TRACK_DEFAULT; + case REQUEST: + return NetworkRequestProto.TYPE_REQUEST; + case BACKGROUND_REQUEST: + return NetworkRequestProto.TYPE_BACKGROUND_REQUEST; + default: + return NetworkRequestProto.TYPE_UNKNOWN; + } + } + + /** @hide */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type)); + proto.write(NetworkRequestProto.REQUEST_ID, requestId); + proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType); + networkCapabilities.dumpDebug(proto, NetworkRequestProto.NETWORK_CAPABILITIES); + + proto.end(token); + } + + public boolean equals(Object obj) { + if (obj instanceof NetworkRequest == false) return false; + NetworkRequest that = (NetworkRequest)obj; + return (that.legacyType == this.legacyType && + that.requestId == this.requestId && + that.type == this.type && + Objects.equals(that.networkCapabilities, this.networkCapabilities)); + } + + public int hashCode() { + return Objects.hash(requestId, legacyType, networkCapabilities, type); + } +} diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java new file mode 100644 index 0000000000..8be4af7b13 --- /dev/null +++ b/framework/src/android/net/NetworkUtils.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2008 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; + +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.system.ErrnoException; +import android.util.Log; +import android.util.Pair; + +import com.android.net.module.util.Inet4AddressUtils; + +import java.io.FileDescriptor; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Locale; +import java.util.TreeSet; + +/** + * Native methods for managing network interfaces. + * + * {@hide} + */ +public class NetworkUtils { + + private static final String TAG = "NetworkUtils"; + + /** + * Attaches a socket filter that drops all of incoming packets. + * @param fd the socket's {@link FileDescriptor}. + */ + public static native void attachDropAllBPFFilter(FileDescriptor fd) throws SocketException; + + /** + * Detaches a socket filter. + * @param fd the socket's {@link FileDescriptor}. + */ + public static native void detachBPFFilter(FileDescriptor fd) throws SocketException; + + /** + * Binds the current process to the network designated by {@code netId}. All sockets created + * in the future (and not explicitly bound via a bound {@link SocketFactory} (see + * {@link Network#getSocketFactory}) will be bound to this network. Note that if this + * {@code Network} ever disconnects all sockets created in this way will cease to work. This + * is by design so an application doesn't accidentally use sockets it thinks are still bound to + * a particular {@code Network}. Passing NETID_UNSET clears the binding. + */ + public native static boolean bindProcessToNetwork(int netId); + + /** + * Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if + * {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}. + */ + public native static int getBoundNetworkForProcess(); + + /** + * Binds host resolutions performed by this process to the network designated by {@code netId}. + * {@link #bindProcessToNetwork} takes precedence over this setting. Passing NETID_UNSET clears + * the binding. + * + * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature(). + */ + @Deprecated + public native static boolean bindProcessToNetworkForHostResolution(int netId); + + /** + * Explicitly binds {@code fd} to the network designated by {@code netId}. This + * overrides any binding via {@link #bindProcessToNetwork}. + * @return 0 on success or negative errno on failure. + */ + public static native int bindSocketToNetwork(FileDescriptor fd, int netId); + + /** + * Protect {@code fd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static native boolean protectFromVpn(FileDescriptor fd); + + /** + * Protect {@code socketfd} from VPN connections. After protecting, data sent through + * this socket will go directly to the underlying network, so its traffic will not be + * forwarded through the VPN. + */ + public native static boolean protectFromVpn(int socketfd); + + /** + * Determine if {@code uid} can access network designated by {@code netId}. + * @return {@code true} if {@code uid} can access network, {@code false} otherwise. + */ + public native static boolean queryUserAccess(int uid, int netId); + + /** + * DNS resolver series jni method. + * Issue the query {@code msg} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkSend( + int netId, byte[] msg, int msglen, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated + * with Domain Name {@code dname} on the network designated by {@code netId}. + * {@code flags} is an additional config to control actual querying behavior. + * @return a file descriptor to watch for read events + */ + public static native FileDescriptor resNetworkQuery( + int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException; + + /** + * DNS resolver series jni method. + * Read a result for the query associated with the {@code fd}. + * @return DnsResponse containing blob answer and rcode + */ + public static native DnsResolver.DnsResponse resNetworkResult(FileDescriptor fd) + throws ErrnoException; + + /** + * DNS resolver series jni method. + * Attempts to cancel the in-progress query associated with the {@code fd}. + */ + public static native void resNetworkCancel(FileDescriptor fd); + + /** + * DNS resolver series jni method. + * Attempts to get network which resolver will use if no network is explicitly selected. + */ + public static native Network getDnsNetwork() throws ErrnoException; + + /** + * Get the tcp repair window associated with the {@code fd}. + * + * @param fd the tcp socket's {@link FileDescriptor}. + * @return a {@link TcpRepairWindow} object indicates tcp window size. + */ + public static native TcpRepairWindow getTcpRepairWindow(FileDescriptor fd) + throws ErrnoException; + + /** + * @see Inet4AddressUtils#intToInet4AddressHTL(int) + * @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)} + * or {@link Inet4AddressUtils#intToInet4AddressHTL(int)} + */ + @Deprecated + @UnsupportedAppUsage + public static InetAddress intToInetAddress(int hostAddress) { + return Inet4AddressUtils.intToInet4AddressHTL(hostAddress); + } + + /** + * @see Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address) + * @deprecated Use either {@link Inet4AddressUtils#inet4AddressToIntHTH(Inet4Address)} + * or {@link Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)} + */ + @Deprecated + public static int inetAddressToInt(Inet4Address inetAddr) + throws IllegalArgumentException { + return Inet4AddressUtils.inet4AddressToIntHTL(inetAddr); + } + + /** + * @see Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int) + * @deprecated Use either {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTH(int)} + * or {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)} + */ + @Deprecated + @UnsupportedAppUsage + public static int prefixLengthToNetmaskInt(int prefixLength) + throws IllegalArgumentException { + return Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL(prefixLength); + } + + /** + * Convert a IPv4 netmask integer to a prefix length + * @param netmask as an integer (0xff000000 for a /8 subnet) + * @return the network prefix length + */ + public static int netmaskIntToPrefixLength(int netmask) { + return Integer.bitCount(netmask); + } + + /** + * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous. + * @param netmask as a {@code Inet4Address}. + * @return the network prefix length + * @throws IllegalArgumentException the specified netmask was not contiguous. + * @hide + * @deprecated use {@link Inet4AddressUtils#netmaskToPrefixLength(Inet4Address)} + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated + public static int netmaskToPrefixLength(Inet4Address netmask) { + // This is only here because some apps seem to be using it (@UnsupportedAppUsage). + return Inet4AddressUtils.netmaskToPrefixLength(netmask); + } + + + /** + * Create an InetAddress from a string where the string must be a standard + * representation of a V4 or V6 address. Avoids doing a DNS lookup on failure + * but it will throw an IllegalArgumentException in that case. + * @param addrString + * @return the InetAddress + * @hide + * @deprecated Use {@link InetAddresses#parseNumericAddress(String)}, if possible. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @Deprecated + public static InetAddress numericToInetAddress(String addrString) + throws IllegalArgumentException { + return InetAddress.parseNumericAddress(addrString); + } + + /** + * Masks a raw IP address byte array with the specified prefix length. + */ + public static void maskRawAddress(byte[] array, int prefixLength) { + if (prefixLength < 0 || prefixLength > array.length * 8) { + throw new RuntimeException("IP address with " + array.length + + " bytes has invalid prefix length " + prefixLength); + } + + int offset = prefixLength / 8; + int remainder = prefixLength % 8; + byte mask = (byte)(0xFF << (8 - remainder)); + + if (offset < array.length) array[offset] = (byte)(array[offset] & mask); + + offset++; + + for (; offset < array.length; offset++) { + array[offset] = 0; + } + } + + /** + * Get InetAddress masked with prefixLength. Will never return null. + * @param address the IP address to mask with + * @param prefixLength the prefixLength used to mask the IP + */ + public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { + byte[] array = address.getAddress(); + maskRawAddress(array, prefixLength); + + InetAddress netPart = null; + try { + netPart = InetAddress.getByAddress(array); + } catch (UnknownHostException e) { + throw new RuntimeException("getNetworkPart error - " + e.toString()); + } + return netPart; + } + + /** + * Returns the implicit netmask of an IPv4 address, as was the custom before 1993. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static int getImplicitNetmask(Inet4Address address) { + // Only here because it seems to be used by apps + return Inet4AddressUtils.getImplicitNetmask(address); + } + + /** + * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64". + * @hide + */ + public static Pair parseIpAndMask(String ipAndMaskString) { + InetAddress address = null; + int prefixLength = -1; + try { + String[] pieces = ipAndMaskString.split("/", 2); + prefixLength = Integer.parseInt(pieces[1]); + address = InetAddress.parseNumericAddress(pieces[0]); + } catch (NullPointerException e) { // Null string. + } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. + } catch (NumberFormatException e) { // Non-numeric prefix. + } catch (IllegalArgumentException e) { // Invalid IP address. + } + + if (address == null || prefixLength == -1) { + throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString); + } + + return new Pair(address, prefixLength); + } + + /** + * Convert a 32 char hex string into a Inet6Address. + * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be + * made into an Inet6Address + * @param addrHexString a 32 character hex string representing an IPv6 addr + * @return addr an InetAddress representation for the string + */ + public static InetAddress hexToInet6Address(String addrHexString) + throws IllegalArgumentException { + try { + return numericToInetAddress(String.format(Locale.US, "%s:%s:%s:%s:%s:%s:%s:%s", + addrHexString.substring(0,4), addrHexString.substring(4,8), + addrHexString.substring(8,12), addrHexString.substring(12,16), + addrHexString.substring(16,20), addrHexString.substring(20,24), + addrHexString.substring(24,28), addrHexString.substring(28,32))); + } catch (Exception e) { + Log.e("NetworkUtils", "error in hexToInet6Address(" + addrHexString + "): " + e); + throw new IllegalArgumentException(e); + } + } + + /** + * Trim leading zeros from IPv4 address strings + * Our base libraries will interpret that as octel.. + * Must leave non v4 addresses and host names alone. + * For example, 192.168.000.010 -> 192.168.0.10 + * TODO - fix base libraries and remove this function + * @param addr a string representing an ip addr + * @return a string propertly trimmed + */ + @UnsupportedAppUsage + public static String trimV4AddrZeros(String addr) { + if (addr == null) return null; + String[] octets = addr.split("\\."); + if (octets.length != 4) return addr; + StringBuilder builder = new StringBuilder(16); + String result = null; + for (int i = 0; i < 4; i++) { + try { + if (octets[i].length() > 3) return addr; + builder.append(Integer.parseInt(octets[i])); + } catch (NumberFormatException e) { + return addr; + } + if (i < 3) builder.append('.'); + } + result = builder.toString(); + return result; + } + + /** + * Returns a prefix set without overlaps. + * + * This expects the src set to be sorted from shorter to longer. Results are undefined + * failing this condition. The returned prefix set is sorted in the same order as the + * passed set, with the same comparator. + */ + private static TreeSet deduplicatePrefixSet(final TreeSet src) { + final TreeSet dst = new TreeSet<>(src.comparator()); + // Prefixes match addresses that share their upper part up to their length, therefore + // the only kind of possible overlap in two prefixes is strict inclusion of the longer + // (more restrictive) in the shorter (including equivalence if they have the same + // length). + // Because prefixes in the src set are sorted from shorter to longer, deduplicating + // is done by simply iterating in order, and not adding any longer prefix that is + // already covered by a shorter one. + newPrefixes: + for (IpPrefix newPrefix : src) { + for (IpPrefix existingPrefix : dst) { + if (existingPrefix.containsPrefix(newPrefix)) { + continue newPrefixes; + } + } + dst.add(newPrefix); + } + return dst; + } + + /** + * Returns how many IPv4 addresses match any of the prefixes in the passed ordered set. + * + * Obviously this returns an integral value between 0 and 2**32. + * The behavior is undefined if any of the prefixes is not an IPv4 prefix or if the + * set is not ordered smallest prefix to longer prefix. + * + * @param prefixes the set of prefixes, ordered by length + */ + public static long routedIPv4AddressCount(final TreeSet prefixes) { + long routedIPCount = 0; + for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) { + if (!prefix.isIPv4()) { + Log.wtf(TAG, "Non-IPv4 prefix in routedIPv4AddressCount"); + } + int rank = 32 - prefix.getPrefixLength(); + routedIPCount += 1L << rank; + } + return routedIPCount; + } + + /** + * Returns how many IPv6 addresses match any of the prefixes in the passed ordered set. + * + * This returns a BigInteger between 0 and 2**128. + * The behavior is undefined if any of the prefixes is not an IPv6 prefix or if the + * set is not ordered smallest prefix to longer prefix. + */ + public static BigInteger routedIPv6AddressCount(final TreeSet prefixes) { + BigInteger routedIPCount = BigInteger.ZERO; + for (final IpPrefix prefix : deduplicatePrefixSet(prefixes)) { + if (!prefix.isIPv6()) { + Log.wtf(TAG, "Non-IPv6 prefix in routedIPv6AddressCount"); + } + int rank = 128 - prefix.getPrefixLength(); + routedIPCount = routedIPCount.add(BigInteger.ONE.shiftLeft(rank)); + } + return routedIPCount; + } + +} diff --git a/framework/src/android/net/PacProxySelector.java b/framework/src/android/net/PacProxySelector.java new file mode 100644 index 0000000000..326943a27d --- /dev/null +++ b/framework/src/android/net/PacProxySelector.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013 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; + +import android.os.ServiceManager; +import android.util.Log; + +import com.android.net.IProxyService; + +import com.google.android.collect.Lists; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.Proxy.Type; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +/** + * @hide + */ +public class PacProxySelector extends ProxySelector { + private static final String TAG = "PacProxySelector"; + public static final String PROXY_SERVICE = "com.android.net.IProxyService"; + private static final String SOCKS = "SOCKS "; + private static final String PROXY = "PROXY "; + + private IProxyService mProxyService; + private final List mDefaultList; + + public PacProxySelector() { + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + if (mProxyService == null) { + // Added because of b10267814 where mako is restarting. + Log.e(TAG, "PacProxyInstaller: no proxy service"); + } + mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY); + } + + @Override + public List select(URI uri) { + if (mProxyService == null) { + mProxyService = IProxyService.Stub.asInterface( + ServiceManager.getService(PROXY_SERVICE)); + } + if (mProxyService == null) { + Log.e(TAG, "select: no proxy service return NO_PROXY"); + return Lists.newArrayList(java.net.Proxy.NO_PROXY); + } + String response = null; + String urlString; + try { + // Strip path and username/password from URI so it's not visible to PAC script. The + // path often contains credentials the app does not want exposed to a potentially + // malicious PAC script. + if (!"http".equalsIgnoreCase(uri.getScheme())) { + uri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), "/", null, null); + } + urlString = uri.toURL().toString(); + } catch (URISyntaxException e) { + urlString = uri.getHost(); + } catch (MalformedURLException e) { + urlString = uri.getHost(); + } + try { + response = mProxyService.resolvePacFile(uri.getHost(), urlString); + } catch (Exception e) { + Log.e(TAG, "Error resolving PAC File", e); + } + if (response == null) { + return mDefaultList; + } + + return parseResponse(response); + } + + private static List parseResponse(String response) { + String[] split = response.split(";"); + List ret = Lists.newArrayList(); + for (String s : split) { + String trimmed = s.trim(); + if (trimmed.equals("DIRECT")) { + ret.add(java.net.Proxy.NO_PROXY); + } else if (trimmed.startsWith(PROXY)) { + Proxy proxy = proxyFromHostPort(Type.HTTP, trimmed.substring(PROXY.length())); + if (proxy != null) { + ret.add(proxy); + } + } else if (trimmed.startsWith(SOCKS)) { + Proxy proxy = proxyFromHostPort(Type.SOCKS, trimmed.substring(SOCKS.length())); + if (proxy != null) { + ret.add(proxy); + } + } + } + if (ret.size() == 0) { + ret.add(java.net.Proxy.NO_PROXY); + } + return ret; + } + + private static Proxy proxyFromHostPort(Proxy.Type type, String hostPortString) { + try { + String[] hostPort = hostPortString.split(":"); + String host = hostPort[0]; + int port = Integer.parseInt(hostPort[1]); + return new Proxy(type, InetSocketAddress.createUnresolved(host, port)); + } catch (NumberFormatException|ArrayIndexOutOfBoundsException e) { + Log.d(TAG, "Unable to parse proxy " + hostPortString + " " + e); + return null; + } + } + + @Override + public void connectFailed(URI uri, SocketAddress address, IOException failure) { + + } + +} diff --git a/framework/src/android/net/Proxy.java b/framework/src/android/net/Proxy.java new file mode 100644 index 0000000000..03b07e080a --- /dev/null +++ b/framework/src/android/net/Proxy.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2007 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; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; + +import com.android.net.module.util.ProxyUtils; + +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.URI; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A convenience class for accessing the user and default proxy + * settings. + */ +public final class Proxy { + + private static final String TAG = "Proxy"; + + private static final ProxySelector sDefaultProxySelector; + + /** + * Used to notify an app that's caching the proxy that either the default + * connection has changed or any connection's proxy has changed. The new + * proxy should be queried using {@link ConnectivityManager#getDefaultProxy()}. + * + *

This is a protected intent that can only be sent by the system + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; + /** + * Intent extra included with {@link #PROXY_CHANGE_ACTION} intents. + * It describes the new proxy being used (as a {@link ProxyInfo} object). + * @deprecated Because {@code PROXY_CHANGE_ACTION} is sent whenever the proxy + * for any network on the system changes, applications should always use + * {@link ConnectivityManager#getDefaultProxy()} or + * {@link ConnectivityManager#getLinkProperties(Network)}.{@link LinkProperties#getHttpProxy()} + * to get the proxy for the Network(s) they are using. + */ + @Deprecated + public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; + + /** @hide */ + public static final int PROXY_VALID = 0; + /** @hide */ + public static final int PROXY_HOSTNAME_EMPTY = 1; + /** @hide */ + public static final int PROXY_HOSTNAME_INVALID = 2; + /** @hide */ + public static final int PROXY_PORT_EMPTY = 3; + /** @hide */ + public static final int PROXY_PORT_INVALID = 4; + /** @hide */ + public static final int PROXY_EXCLLIST_INVALID = 5; + + private static ConnectivityManager sConnectivityManager = null; + + // Hostname / IP REGEX validation + // Matches blank input, ips, and domain names + private static final String NAME_IP_REGEX = + "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*"; + + private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$"; + + private static final Pattern HOSTNAME_PATTERN; + + private static final String EXCL_REGEX = + "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*"; + + private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$"; + + private static final Pattern EXCLLIST_PATTERN; + + static { + HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); + EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); + sDefaultProxySelector = ProxySelector.getDefault(); + } + + /** + * Return the proxy object to be used for the URL given as parameter. + * @param ctx A Context used to get the settings for the proxy host. + * @param url A URL to be accessed. Used to evaluate exclusion list. + * @return Proxy (java.net) object containing the host name. If the + * user did not set a hostname it returns the default host. + * A null value means that no host is to be used. + * {@hide} + */ + @UnsupportedAppUsage + public static final java.net.Proxy getProxy(Context ctx, String url) { + String host = ""; + if ((url != null) && !isLocalHost(host)) { + URI uri = URI.create(url); + ProxySelector proxySelector = ProxySelector.getDefault(); + + List proxyList = proxySelector.select(uri); + + if (proxyList.size() > 0) { + return proxyList.get(0); + } + } + return java.net.Proxy.NO_PROXY; + } + + + /** + * Return the proxy host set by the user. + * @param ctx A Context used to get the settings for the proxy host. + * @return String containing the host name. If the user did not set a host + * name it returns the default host. A null value means that no + * host is to be used. + * @deprecated Use standard java vm proxy values to find the host, port + * and exclusion list. This call ignores the exclusion list. + */ + @Deprecated + public static final String getHost(Context ctx) { + java.net.Proxy proxy = getProxy(ctx, null); + if (proxy == java.net.Proxy.NO_PROXY) return null; + try { + return ((InetSocketAddress)(proxy.address())).getHostName(); + } catch (Exception e) { + return null; + } + } + + /** + * Return the proxy port set by the user. + * @param ctx A Context used to get the settings for the proxy port. + * @return The port number to use or -1 if no proxy is to be used. + * @deprecated Use standard java vm proxy values to find the host, port + * and exclusion list. This call ignores the exclusion list. + */ + @Deprecated + public static final int getPort(Context ctx) { + java.net.Proxy proxy = getProxy(ctx, null); + if (proxy == java.net.Proxy.NO_PROXY) return -1; + try { + return ((InetSocketAddress)(proxy.address())).getPort(); + } catch (Exception e) { + return -1; + } + } + + /** + * Return the default proxy host specified by the carrier. + * @return String containing the host name or null if there is no proxy for + * this carrier. + * @deprecated Use standard java vm proxy values to find the host, port and + * exclusion list. This call ignores the exclusion list and no + * longer reports only mobile-data apn-based proxy values. + */ + @Deprecated + public static final String getDefaultHost() { + String host = System.getProperty("http.proxyHost"); + if (TextUtils.isEmpty(host)) return null; + return host; + } + + /** + * Return the default proxy port specified by the carrier. + * @return The port number to be used with the proxy host or -1 if there is + * no proxy for this carrier. + * @deprecated Use standard java vm proxy values to find the host, port and + * exclusion list. This call ignores the exclusion list and no + * longer reports only mobile-data apn-based proxy values. + */ + @Deprecated + public static final int getDefaultPort() { + if (getDefaultHost() == null) return -1; + try { + return Integer.parseInt(System.getProperty("http.proxyPort")); + } catch (NumberFormatException e) { + return -1; + } + } + + private static final boolean isLocalHost(String host) { + if (host == null) { + return false; + } + try { + if (host != null) { + if (host.equalsIgnoreCase("localhost")) { + return true; + } + if (InetAddresses.parseNumericAddress(host).isLoopbackAddress()) { + return true; + } + } + } catch (IllegalArgumentException iex) { + } + return false; + } + + /** + * Validate syntax of hostname, port and exclusion list entries + * {@hide} + */ + public static int validate(String hostname, String port, String exclList) { + Matcher match = HOSTNAME_PATTERN.matcher(hostname); + Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList); + + if (!match.matches()) return PROXY_HOSTNAME_INVALID; + + if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID; + + if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY; + + if (port.length() > 0) { + if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY; + int portVal = -1; + try { + portVal = Integer.parseInt(port); + } catch (NumberFormatException ex) { + return PROXY_PORT_INVALID; + } + if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID; + } + return PROXY_VALID; + } + + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public static final void setHttpProxySystemProperty(ProxyInfo p) { + String host = null; + String port = null; + String exclList = null; + Uri pacFileUrl = Uri.EMPTY; + if (p != null) { + host = p.getHost(); + port = Integer.toString(p.getPort()); + exclList = ProxyUtils.exclusionListAsString(p.getExclusionList()); + pacFileUrl = p.getPacFileUrl(); + } + setHttpProxySystemProperty(host, port, exclList, pacFileUrl); + } + + /** @hide */ + public static final void setHttpProxySystemProperty(String host, String port, String exclList, + Uri pacFileUrl) { + if (exclList != null) exclList = exclList.replace(",", "|"); + if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList); + if (host != null) { + System.setProperty("http.proxyHost", host); + System.setProperty("https.proxyHost", host); + } else { + System.clearProperty("http.proxyHost"); + System.clearProperty("https.proxyHost"); + } + if (port != null) { + System.setProperty("http.proxyPort", port); + System.setProperty("https.proxyPort", port); + } else { + System.clearProperty("http.proxyPort"); + System.clearProperty("https.proxyPort"); + } + if (exclList != null) { + System.setProperty("http.nonProxyHosts", exclList); + System.setProperty("https.nonProxyHosts", exclList); + } else { + System.clearProperty("http.nonProxyHosts"); + System.clearProperty("https.nonProxyHosts"); + } + if (!Uri.EMPTY.equals(pacFileUrl)) { + ProxySelector.setDefault(new PacProxySelector()); + } else { + ProxySelector.setDefault(sDefaultProxySelector); + } + } +} diff --git a/framework/src/android/net/ProxyInfo.aidl b/framework/src/android/net/ProxyInfo.aidl new file mode 100644 index 0000000000..a5d0c120e7 --- /dev/null +++ b/framework/src/android/net/ProxyInfo.aidl @@ -0,0 +1,21 @@ +/* +** +** Copyright (C) 2010 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; + +@JavaOnlyStableParcelable parcelable ProxyInfo; + diff --git a/framework/src/android/net/ProxyInfo.java b/framework/src/android/net/ProxyInfo.java new file mode 100644 index 0000000000..c9bca2876b --- /dev/null +++ b/framework/src/android/net/ProxyInfo.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2010 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.net.InetSocketAddress; +import java.net.URLConnection; +import java.util.List; +import java.util.Locale; + +/** + * Describes a proxy configuration. + * + * Proxy configurations are already integrated within the {@code java.net} and + * Apache HTTP stack. So {@link URLConnection} and Apache's {@code HttpClient} will use + * them automatically. + * + * Other HTTP stacks will need to obtain the proxy info from + * {@link Proxy#PROXY_CHANGE_ACTION} broadcast as the extra {@link Proxy#EXTRA_PROXY_INFO}. + */ +public class ProxyInfo implements Parcelable { + + private final String mHost; + private final int mPort; + private final String mExclusionList; + private final String[] mParsedExclusionList; + private final Uri mPacFileUrl; + + /** + *@hide + */ + public static final String LOCAL_EXCL_LIST = ""; + /** + *@hide + */ + public static final int LOCAL_PORT = -1; + /** + *@hide + */ + public static final String LOCAL_HOST = "localhost"; + + /** + * Constructs a {@link ProxyInfo} object that points at a Direct proxy + * on the specified host and port. + */ + public static ProxyInfo buildDirectProxy(String host, int port) { + return new ProxyInfo(host, port, null); + } + + /** + * Constructs a {@link ProxyInfo} object that points at a Direct proxy + * on the specified host and port. + * + * The proxy will not be used to access any host in exclusion list, exclList. + * + * @param exclList Hosts to exclude using the proxy on connections for. These + * hosts can use wildcards such as *.example.com. + */ + public static ProxyInfo buildDirectProxy(String host, int port, List exclList) { + String[] array = exclList.toArray(new String[exclList.size()]); + return new ProxyInfo(host, port, TextUtils.join(",", array), array); + } + + /** + * Construct a {@link ProxyInfo} that will download and run the PAC script + * at the specified URL. + */ + public static ProxyInfo buildPacProxy(Uri pacUri) { + return new ProxyInfo(pacUri); + } + + /** + * Construct a {@link ProxyInfo} object that will download and run the PAC script at the + * specified URL and port. + */ + @NonNull + public static ProxyInfo buildPacProxy(@NonNull Uri pacUrl, int port) { + return new ProxyInfo(pacUrl, port); + } + + /** + * Create a ProxyProperties that points at a HTTP Proxy. + * @hide + */ + @UnsupportedAppUsage + public ProxyInfo(String host, int port, String exclList) { + mHost = host; + mPort = port; + mExclusionList = exclList; + mParsedExclusionList = parseExclusionList(mExclusionList); + mPacFileUrl = Uri.EMPTY; + } + + /** + * Create a ProxyProperties that points at a PAC URL. + * @hide + */ + public ProxyInfo(@NonNull Uri pacFileUrl) { + mHost = LOCAL_HOST; + mPort = LOCAL_PORT; + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); + if (pacFileUrl == null) { + throw new NullPointerException(); + } + mPacFileUrl = pacFileUrl; + } + + /** + * Only used in PacProxyInstaller after Local Proxy is bound. + * @hide + */ + public ProxyInfo(@NonNull Uri pacFileUrl, int localProxyPort) { + mHost = LOCAL_HOST; + mPort = localProxyPort; + mExclusionList = LOCAL_EXCL_LIST; + mParsedExclusionList = parseExclusionList(mExclusionList); + if (pacFileUrl == null) { + throw new NullPointerException(); + } + mPacFileUrl = pacFileUrl; + } + + private static String[] parseExclusionList(String exclusionList) { + if (exclusionList == null) { + return new String[0]; + } else { + return exclusionList.toLowerCase(Locale.ROOT).split(","); + } + } + + private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) { + mHost = host; + mPort = port; + mExclusionList = exclList; + mParsedExclusionList = parsedExclList; + mPacFileUrl = Uri.EMPTY; + } + + /** + * A copy constructor to hold proxy properties. + */ + public ProxyInfo(@Nullable ProxyInfo source) { + if (source != null) { + mHost = source.getHost(); + mPort = source.getPort(); + mPacFileUrl = source.mPacFileUrl; + mExclusionList = source.getExclusionListAsString(); + mParsedExclusionList = source.mParsedExclusionList; + } else { + mHost = null; + mPort = 0; + mExclusionList = null; + mParsedExclusionList = null; + mPacFileUrl = Uri.EMPTY; + } + } + + /** + * @hide + */ + public InetSocketAddress getSocketAddress() { + InetSocketAddress inetSocketAddress = null; + try { + inetSocketAddress = new InetSocketAddress(mHost, mPort); + } catch (IllegalArgumentException e) { } + return inetSocketAddress; + } + + /** + * Returns the URL of the current PAC script or null if there is + * no PAC script. + */ + public Uri getPacFileUrl() { + return mPacFileUrl; + } + + /** + * When configured to use a Direct Proxy this returns the host + * of the proxy. + */ + public String getHost() { + return mHost; + } + + /** + * When configured to use a Direct Proxy this returns the port + * of the proxy + */ + public int getPort() { + return mPort; + } + + /** + * When configured to use a Direct Proxy this returns the list + * of hosts for which the proxy is ignored. + */ + public String[] getExclusionList() { + return mParsedExclusionList; + } + + /** + * comma separated + * @hide + */ + @Nullable + public String getExclusionListAsString() { + return mExclusionList; + } + + /** + * Return true if the pattern of proxy is valid, otherwise return false. + */ + public boolean isValid() { + if (!Uri.EMPTY.equals(mPacFileUrl)) return true; + return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, + mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); + } + + /** + * @hide + */ + public java.net.Proxy makeProxy() { + java.net.Proxy proxy = java.net.Proxy.NO_PROXY; + if (mHost != null) { + try { + InetSocketAddress inetSocketAddress = new InetSocketAddress(mHost, mPort); + proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, inetSocketAddress); + } catch (IllegalArgumentException e) { + } + } + return proxy; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (!Uri.EMPTY.equals(mPacFileUrl)) { + sb.append("PAC Script: "); + sb.append(mPacFileUrl); + } + if (mHost != null) { + sb.append("["); + sb.append(mHost); + sb.append("] "); + sb.append(Integer.toString(mPort)); + if (mExclusionList != null) { + sb.append(" xl=").append(mExclusionList); + } + } else { + sb.append("[ProxyProperties.mHost == null]"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ProxyInfo)) return false; + ProxyInfo p = (ProxyInfo)o; + // If PAC URL is present in either then they must be equal. + // Other parameters will only be for fall back. + if (!Uri.EMPTY.equals(mPacFileUrl)) { + return mPacFileUrl.equals(p.getPacFileUrl()) && mPort == p.mPort; + } + if (!Uri.EMPTY.equals(p.mPacFileUrl)) { + return false; + } + if (mExclusionList != null && !mExclusionList.equals(p.getExclusionListAsString())) { + return false; + } + if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) { + return false; + } + if (mHost != null && p.mHost == null) return false; + if (mHost == null && p.mHost != null) return false; + if (mPort != p.mPort) return false; + return true; + } + + /** + * Implement the Parcelable interface + * @hide + */ + public int describeContents() { + return 0; + } + + @Override + /* + * generate hashcode based on significant fields + */ + public int hashCode() { + return ((null == mHost) ? 0 : mHost.hashCode()) + + ((null == mExclusionList) ? 0 : mExclusionList.hashCode()) + + mPort; + } + + /** + * Implement the Parcelable interface. + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + if (!Uri.EMPTY.equals(mPacFileUrl)) { + dest.writeByte((byte)1); + mPacFileUrl.writeToParcel(dest, 0); + dest.writeInt(mPort); + return; + } else { + dest.writeByte((byte)0); + } + if (mHost != null) { + dest.writeByte((byte)1); + dest.writeString(mHost); + dest.writeInt(mPort); + } else { + dest.writeByte((byte)0); + } + dest.writeString(mExclusionList); + dest.writeStringArray(mParsedExclusionList); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public ProxyInfo createFromParcel(Parcel in) { + String host = null; + int port = 0; + if (in.readByte() != 0) { + Uri url = Uri.CREATOR.createFromParcel(in); + int localPort = in.readInt(); + return new ProxyInfo(url, localPort); + } + if (in.readByte() != 0) { + host = in.readString(); + port = in.readInt(); + } + String exclList = in.readString(); + String[] parsedExclList = in.createStringArray(); + ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList); + return proxyProperties; + } + + public ProxyInfo[] newArray(int size) { + return new ProxyInfo[size]; + } + }; +} diff --git a/framework/src/android/net/RouteInfo.aidl b/framework/src/android/net/RouteInfo.aidl new file mode 100644 index 0000000000..7af9fdaef3 --- /dev/null +++ b/framework/src/android/net/RouteInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2011 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; + +@JavaOnlyStableParcelable parcelable RouteInfo; diff --git a/framework/src/android/net/RouteInfo.java b/framework/src/android/net/RouteInfo.java new file mode 100644 index 0000000000..94f849f006 --- /dev/null +++ b/framework/src/android/net/RouteInfo.java @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2011 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; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.net.module.util.NetUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Objects; + +/** + * Represents a network route. + *

+ * This is used both to describe static network configuration and live network + * configuration information. + * + * A route contains three pieces of information: + *

    + *
  • a destination {@link IpPrefix} specifying the network destinations covered by this route. + * If this is {@code null} it indicates a default route of the address family (IPv4 or IPv6) + * implied by the gateway IP address. + *
  • a gateway {@link InetAddress} indicating the next hop to use. If this is {@code null} it + * indicates a directly-connected route. + *
  • an interface (which may be unspecified). + *
+ * Either the destination or the gateway may be {@code null}, but not both. If the + * destination and gateway are both specified, they must be of the same address family + * (IPv4 or IPv6). + */ +public final class RouteInfo implements Parcelable { + /** @hide */ + @IntDef(value = { + RTN_UNICAST, + RTN_UNREACHABLE, + RTN_THROW, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RouteType {} + + /** + * The IP destination address for this route. + */ + @NonNull + private final IpPrefix mDestination; + + /** + * The gateway address for this route. + */ + @UnsupportedAppUsage + @Nullable + private final InetAddress mGateway; + + /** + * The interface for this route. + */ + @Nullable + private final String mInterface; + + + /** Unicast route. @hide */ + @SystemApi + public static final int RTN_UNICAST = 1; + + /** Unreachable route. @hide */ + @SystemApi + public static final int RTN_UNREACHABLE = 7; + + /** Throw route. @hide */ + @SystemApi + public static final int RTN_THROW = 9; + + /** + * The type of this route; one of the RTN_xxx constants above. + */ + private final int mType; + + /** + * The maximum transmission unit size for this route. + */ + private final int mMtu; + + // Derived data members. + // TODO: remove these. + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private final boolean mIsHost; + private final boolean mHasGateway; + + /** + * Constructs a RouteInfo object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if gateway is an instance of + * {@link Inet6Address}. + *

+ * destination and gateway may not both be null. + * + * @param destination the destination prefix + * @param gateway the IP address to route packets through + * @param iface the interface name to send packets on + * @param type the type of this route + * + * @hide + */ + @SystemApi + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface, @RouteType int type) { + this(destination, gateway, iface, type, 0); + } + + /** + * Constructs a RouteInfo object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if gateway is an instance of + * {@link Inet6Address}. + *

+ * destination and gateway may not both be null. + * + * @param destination the destination prefix + * @param gateway the IP address to route packets through + * @param iface the interface name to send packets on + * @param type the type of this route + * @param mtu the maximum transmission unit size for this route + * + * @hide + */ + @SystemApi + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface, @RouteType int type, int mtu) { + switch (type) { + case RTN_UNICAST: + case RTN_UNREACHABLE: + case RTN_THROW: + // TODO: It would be nice to ensure that route types that don't have nexthops or + // interfaces, such as unreachable or throw, can't be created if an interface or + // a gateway is specified. This is a bit too complicated to do at the moment + // because: + // + // - LinkProperties sets the interface on routes added to it, and modifies the + // interfaces of all the routes when its interface name changes. + // - Even when the gateway is null, we store a non-null gateway here. + // + // For now, we just rely on the code that sets routes to do things properly. + break; + default: + throw new IllegalArgumentException("Unknown route type " + type); + } + + if (destination == null) { + if (gateway != null) { + if (gateway instanceof Inet4Address) { + destination = new IpPrefix(Inet4Address.ANY, 0); + } else { + destination = new IpPrefix(Inet6Address.ANY, 0); + } + } else { + // no destination, no gateway. invalid. + throw new IllegalArgumentException("Invalid arguments passed in: " + gateway + "," + + destination); + } + } + // TODO: set mGateway to null if there is no gateway. This is more correct, saves space, and + // matches the documented behaviour. Before we can do this we need to fix all callers (e.g., + // ConnectivityService) to stop doing things like r.getGateway().equals(), ... . + if (gateway == null) { + if (destination.getAddress() instanceof Inet4Address) { + gateway = Inet4Address.ANY; + } else { + gateway = Inet6Address.ANY; + } + } + mHasGateway = (!gateway.isAnyLocalAddress()); + + if ((destination.getAddress() instanceof Inet4Address + && !(gateway instanceof Inet4Address)) + || (destination.getAddress() instanceof Inet6Address + && !(gateway instanceof Inet6Address))) { + throw new IllegalArgumentException("address family mismatch in RouteInfo constructor"); + } + mDestination = destination; // IpPrefix objects are immutable. + mGateway = gateway; // InetAddress objects are immutable. + mInterface = iface; // Strings are immutable. + mType = type; + mIsHost = isHost(); + mMtu = mtu; + } + + /** + * Constructs a {@code RouteInfo} object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if gateway is an instance of {@link Inet6Address}. + *

+ * Destination and gateway may not both be null. + * + * @param destination the destination address and prefix in an {@link IpPrefix} + * @param gateway the {@link InetAddress} to route packets through + * @param iface the interface name to send packets on + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface) { + this(destination, gateway, iface, RTN_UNICAST); + } + + /** + * @hide + */ + @UnsupportedAppUsage + public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway, + @Nullable String iface) { + this(destination == null ? null : + new IpPrefix(destination.getAddress(), destination.getPrefixLength()), + gateway, iface); + } + + /** + * Constructs a {@code RouteInfo} object. + * + * If destination is null, then gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if gateway is an instance of {@link Inet6Address}. + *

+ * Destination and gateway may not both be null. + * + * @param destination the destination address and prefix in an {@link IpPrefix} + * @param gateway the {@link InetAddress} to route packets through + * + * @hide + */ + public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway) { + this(destination, gateway, null); + } + + /** + * @hide + * + * TODO: Remove this. + */ + @UnsupportedAppUsage + public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway) { + this(destination, gateway, null); + } + + /** + * Constructs a default {@code RouteInfo} object. + * + * @param gateway the {@link InetAddress} to route packets through + * + * @hide + */ + @UnsupportedAppUsage + public RouteInfo(@NonNull InetAddress gateway) { + this((IpPrefix) null, gateway, null); + } + + /** + * Constructs a {@code RouteInfo} object representing a direct connected subnet. + * + * @param destination the {@link IpPrefix} describing the address and prefix + * length of the subnet. + * + * @hide + */ + public RouteInfo(@NonNull IpPrefix destination) { + this(destination, null, null); + } + + /** + * @hide + */ + public RouteInfo(@NonNull LinkAddress destination) { + this(destination, null, null); + } + + /** + * @hide + */ + public RouteInfo(@NonNull IpPrefix destination, @RouteType int type) { + this(destination, null, null, type); + } + + /** + * @hide + */ + public static RouteInfo makeHostRoute(@NonNull InetAddress host, @Nullable String iface) { + return makeHostRoute(host, null, iface); + } + + /** + * @hide + */ + public static RouteInfo makeHostRoute(@Nullable InetAddress host, @Nullable InetAddress gateway, + @Nullable String iface) { + if (host == null) return null; + + if (host instanceof Inet4Address) { + return new RouteInfo(new IpPrefix(host, 32), gateway, iface); + } else { + return new RouteInfo(new IpPrefix(host, 128), gateway, iface); + } + } + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + private boolean isHost() { + return (mDestination.getAddress() instanceof Inet4Address && + mDestination.getPrefixLength() == 32) || + (mDestination.getAddress() instanceof Inet6Address && + mDestination.getPrefixLength() == 128); + } + + /** + * Retrieves the destination address and prefix length in the form of an {@link IpPrefix}. + * + * @return {@link IpPrefix} specifying the destination. This is never {@code null}. + */ + @NonNull + public IpPrefix getDestination() { + return mDestination; + } + + /** + * TODO: Convert callers to use IpPrefix and then remove. + * @hide + */ + @NonNull + public LinkAddress getDestinationLinkAddress() { + return new LinkAddress(mDestination.getAddress(), mDestination.getPrefixLength()); + } + + /** + * Retrieves the gateway or next hop {@link InetAddress} for this route. + * + * @return {@link InetAddress} specifying the gateway or next hop. This may be + * {@code null} for a directly-connected route." + */ + @Nullable + public InetAddress getGateway() { + return mGateway; + } + + /** + * Retrieves the interface used for this route if specified, else {@code null}. + * + * @return The name of the interface used for this route. + */ + @Nullable + public String getInterface() { + return mInterface; + } + + /** + * Retrieves the type of this route. + * + * @return The type of this route; one of the {@code RTN_xxx} constants defined in this class. + * + * @hide + */ + @SystemApi + @RouteType + public int getType() { + return mType; + } + + /** + * Retrieves the MTU size for this route. + * + * @return The MTU size, or 0 if it has not been set. + * @hide + */ + @SystemApi + public int getMtu() { + return mMtu; + } + + /** + * Indicates if this route is a default route (ie, has no destination specified). + * + * @return {@code true} if the destination has a prefix length of 0. + */ + public boolean isDefaultRoute() { + return mType == RTN_UNICAST && mDestination.getPrefixLength() == 0; + } + + /** + * Indicates if this route is an unreachable default route. + * + * @return {@code true} if it's an unreachable route with prefix length of 0. + * @hide + */ + private boolean isUnreachableDefaultRoute() { + return mType == RTN_UNREACHABLE && mDestination.getPrefixLength() == 0; + } + + /** + * Indicates if this route is an IPv4 default route. + * @hide + */ + public boolean isIPv4Default() { + return isDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; + } + + /** + * Indicates if this route is an IPv4 unreachable default route. + * @hide + */ + public boolean isIPv4UnreachableDefault() { + return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; + } + + /** + * Indicates if this route is an IPv6 default route. + * @hide + */ + public boolean isIPv6Default() { + return isDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; + } + + /** + * Indicates if this route is an IPv6 unreachable default route. + * @hide + */ + public boolean isIPv6UnreachableDefault() { + return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; + } + + /** + * Indicates if this route is a host route (ie, matches only a single host address). + * + * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6, + * respectively. + * @hide + */ + public boolean isHostRoute() { + return mIsHost; + } + + /** + * Indicates if this route has a next hop ({@code true}) or is directly-connected + * ({@code false}). + * + * @return {@code true} if a gateway is specified + */ + public boolean hasGateway() { + return mHasGateway; + } + + /** + * Determines whether the destination and prefix of this route includes the specified + * address. + * + * @param destination A {@link InetAddress} to test to see if it would match this route. + * @return {@code true} if the destination and prefix length cover the given address. + */ + public boolean matches(InetAddress destination) { + return mDestination.contains(destination); + } + + /** + * Find the route from a Collection of routes that best matches a given address. + * May return null if no routes are applicable. + * @param routes a Collection of RouteInfos to chose from + * @param dest the InetAddress your trying to get to + * @return the RouteInfo from the Collection that best fits the given address + * + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Nullable + public static RouteInfo selectBestRoute(Collection routes, InetAddress dest) { + return NetUtils.selectBestRoute(routes, dest); + } + + /** + * Returns a human-readable description of this object. + */ + public String toString() { + String val = ""; + if (mDestination != null) val = mDestination.toString(); + if (mType == RTN_UNREACHABLE) { + val += " unreachable"; + } else if (mType == RTN_THROW) { + val += " throw"; + } else { + val += " ->"; + if (mGateway != null) val += " " + mGateway.getHostAddress(); + if (mInterface != null) val += " " + mInterface; + if (mType != RTN_UNICAST) { + val += " unknown type " + mType; + } + } + val += " mtu " + mMtu; + return val; + } + + /** + * Compares this RouteInfo object against the specified object and indicates if they are equal. + * @return {@code true} if the objects are equal, {@code false} otherwise. + */ + public boolean equals(Object obj) { + if (this == obj) return true; + + if (!(obj instanceof RouteInfo)) return false; + + RouteInfo target = (RouteInfo) obj; + + return Objects.equals(mDestination, target.getDestination()) && + Objects.equals(mGateway, target.getGateway()) && + Objects.equals(mInterface, target.getInterface()) && + mType == target.getType() && mMtu == target.getMtu(); + } + + /** + * A helper class that contains the destination, the gateway and the interface in a + * {@code RouteInfo}, used by {@link ConnectivityService#updateRoutes} or + * {@link LinkProperties#addRoute} to calculate the list to be updated. + * {@code RouteInfo} objects with different interfaces are treated as different routes because + * *usually* on Android different interfaces use different routing tables, and moving a route + * to a new routing table never constitutes an update, but is always a remove and an add. + * + * @hide + */ + public static class RouteKey { + @NonNull private final IpPrefix mDestination; + @Nullable private final InetAddress mGateway; + @Nullable private final String mInterface; + + RouteKey(@NonNull IpPrefix destination, @Nullable InetAddress gateway, + @Nullable String iface) { + mDestination = destination; + mGateway = gateway; + mInterface = iface; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RouteKey)) { + return false; + } + RouteKey p = (RouteKey) o; + // No need to do anything special for scoped addresses. Inet6Address#equals does not + // consider the scope ID, but the netd route IPCs (e.g., INetd#networkAddRouteParcel) + // and the kernel ignore scoped addresses both in the prefix and in the nexthop and only + // look at RTA_OIF. + return Objects.equals(p.mDestination, mDestination) + && Objects.equals(p.mGateway, mGateway) + && Objects.equals(p.mInterface, mInterface); + } + + @Override + public int hashCode() { + return Objects.hash(mDestination, mGateway, mInterface); + } + } + + /** + * Get {@code RouteKey} of this {@code RouteInfo}. + * @return a {@code RouteKey} object. + * + * @hide + */ + @NonNull + public RouteKey getRouteKey() { + return new RouteKey(mDestination, mGateway, mInterface); + } + + /** + * Returns a hashcode for this RouteInfo object. + */ + public int hashCode() { + return (mDestination.hashCode() * 41) + + (mGateway == null ? 0 :mGateway.hashCode() * 47) + + (mInterface == null ? 0 :mInterface.hashCode() * 67) + + (mType * 71) + (mMtu * 89); + } + + /** + * Implement the Parcelable interface + */ + public int describeContents() { + return 0; + } + + /** + * Implement the Parcelable interface + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mDestination, flags); + byte[] gatewayBytes = (mGateway == null) ? null : mGateway.getAddress(); + dest.writeByteArray(gatewayBytes); + dest.writeString(mInterface); + dest.writeInt(mType); + dest.writeInt(mMtu); + } + + /** + * Implement the Parcelable interface. + */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public RouteInfo createFromParcel(Parcel in) { + IpPrefix dest = in.readParcelable(null); + + InetAddress gateway = null; + byte[] addr = in.createByteArray(); + try { + gateway = InetAddress.getByAddress(addr); + } catch (UnknownHostException e) {} + + String iface = in.readString(); + int type = in.readInt(); + int mtu = in.readInt(); + + return new RouteInfo(dest, gateway, iface, type, mtu); + } + + public RouteInfo[] newArray(int size) { + return new RouteInfo[size]; + } + }; +} diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java new file mode 100644 index 0000000000..d007a9520c --- /dev/null +++ b/framework/src/android/net/SocketKeepalive.java @@ -0,0 +1,301 @@ +/* + * 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; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Binder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * Allows applications to request that the system periodically send specific packets on their + * behalf, using hardware offload to save battery power. + * + * To request that the system send keepalives, call one of the methods that return a + * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive}, + * passing in a non-null callback. If the {@link SocketKeepalive} is successfully + * started, the callback's {@code onStarted} method will be called. If an error occurs, + * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this + * class. + * + * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call + * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or + * {@link SocketKeepalive.Callback#onError} if an error occurred. + * + * For cellular, the device MUST support at least 1 keepalive slot. + * + * For WiFi, the device SHOULD support keepalive offload. If it does not, it MUST reply with + * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload + * request. If it does, it MUST support at least 3 concurrent keepalive slots. + */ +public abstract class SocketKeepalive implements AutoCloseable { + static final String TAG = "SocketKeepalive"; + + /** + * No errors. + * @hide + */ + @SystemApi + public static final int SUCCESS = 0; + + /** @hide */ + public static final int NO_KEEPALIVE = -1; + + /** @hide */ + public static final int DATA_RECEIVED = -2; + + /** @hide */ + public static final int BINDER_DIED = -10; + + /** The specified {@code Network} is not connected. */ + public static final int ERROR_INVALID_NETWORK = -20; + /** The specified IP addresses are invalid. For example, the specified source IP address is + * not configured on the specified {@code Network}. */ + public static final int ERROR_INVALID_IP_ADDRESS = -21; + /** The requested port is invalid. */ + public static final int ERROR_INVALID_PORT = -22; + /** The packet length is invalid (e.g., too long). */ + public static final int ERROR_INVALID_LENGTH = -23; + /** The packet transmission interval is invalid (e.g., too short). */ + public static final int ERROR_INVALID_INTERVAL = -24; + /** The target socket is invalid. */ + public static final int ERROR_INVALID_SOCKET = -25; + /** The target socket is not idle. */ + public static final int ERROR_SOCKET_NOT_IDLE = -26; + /** + * The stop reason is uninitialized. This should only be internally used as initial state + * of stop reason, instead of propagating to application. + * @hide + */ + public static final int ERROR_STOP_REASON_UNINITIALIZED = -27; + + /** The device does not support this request. */ + public static final int ERROR_UNSUPPORTED = -30; + /** @hide TODO: delete when telephony code has been updated. */ + public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; + /** The limitation of resource is reached. */ + public static final int ERROR_INSUFFICIENT_RESOURCES = -32; + + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ERROR_" }, value = { + ERROR_INVALID_NETWORK, + ERROR_INVALID_IP_ADDRESS, + ERROR_INVALID_PORT, + ERROR_INVALID_LENGTH, + ERROR_INVALID_INTERVAL, + ERROR_INVALID_SOCKET, + ERROR_SOCKET_NOT_IDLE + }) + public @interface ErrorCode {} + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + SUCCESS, + ERROR_INVALID_LENGTH, + ERROR_UNSUPPORTED, + ERROR_INSUFFICIENT_RESOURCES, + ERROR_HARDWARE_UNSUPPORTED + }) + public @interface KeepaliveEvent {} + + /** + * The minimum interval in seconds between keepalive packet transmissions. + * + * @hide + **/ + public static final int MIN_INTERVAL_SEC = 10; + + /** + * The maximum interval in seconds between keepalive packet transmissions. + * + * @hide + **/ + public static final int MAX_INTERVAL_SEC = 3600; + + /** + * An exception that embarks an error code. + * @hide + */ + public static class ErrorCodeException extends Exception { + public final int error; + public ErrorCodeException(final int error, final Throwable e) { + super(e); + this.error = error; + } + public ErrorCodeException(final int error) { + this.error = error; + } + } + + /** + * This socket is invalid. + * See the error code for details, and the optional cause. + * @hide + */ + public static class InvalidSocketException extends ErrorCodeException { + public InvalidSocketException(final int error, final Throwable e) { + super(error, e); + } + public InvalidSocketException(final int error) { + super(error); + } + } + + @NonNull final IConnectivityManager mService; + @NonNull final Network mNetwork; + @NonNull final ParcelFileDescriptor mPfd; + @NonNull final Executor mExecutor; + @NonNull final ISocketKeepaliveCallback mCallback; + // TODO: remove slot since mCallback could be used to identify which keepalive to stop. + @Nullable Integer mSlot; + + SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + @NonNull Executor executor, @NonNull Callback callback) { + mService = service; + mNetwork = network; + mPfd = pfd; + mExecutor = executor; + mCallback = new ISocketKeepaliveCallback.Stub() { + @Override + public void onStarted(int slot) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> { + mSlot = slot; + callback.onStarted(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onStopped() { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onStopped(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onError(int error) { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onError(error); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onDataReceived() { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + mSlot = null; + callback.onDataReceived(); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + } + + /** + * Request that keepalive be started with the given {@code intervalSec}. See + * {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an exception + * when invoking start or stop of the {@link SocketKeepalive}, a {@link RemoteException} will be + * thrown into the {@code executor}. This is typically not important to catch because the remote + * party is the system, so if it is not in shape to communicate through binder the system is + * probably going down anyway. If the caller cares regardless, it can use a custom + * {@link Executor} to catch the {@link RemoteException}. + * + * @param intervalSec The target interval in seconds between keepalive packet transmissions. + * The interval should be between 10 seconds and 3600 seconds, otherwise + * {@link #ERROR_INVALID_INTERVAL} will be returned. + */ + public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC) + int intervalSec) { + startImpl(intervalSec); + } + + abstract void startImpl(int intervalSec); + + /** + * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped} + * before using the object. See {@link SocketKeepalive}. + */ + public final void stop() { + stopImpl(); + } + + abstract void stopImpl(); + + /** + * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be + * usable again if {@code close()} is called. + */ + @Override + public final void close() { + stop(); + try { + mPfd.close(); + } catch (IOException e) { + // Nothing much can be done. + } + } + + /** + * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See + * {@link SocketKeepalive}. + */ + public static class Callback { + /** The requested keepalive was successfully started. */ + public void onStarted() {} + /** The keepalive was successfully stopped. */ + public void onStopped() {} + /** An error occurred. */ + public void onError(@ErrorCode int error) {} + /** The keepalive on a TCP socket was stopped because the socket received data. This is + * never called for UDP sockets. */ + public void onDataReceived() {} + } +} diff --git a/framework/src/android/net/StaticIpConfiguration.aidl b/framework/src/android/net/StaticIpConfiguration.aidl new file mode 100644 index 0000000000..8aac701fe7 --- /dev/null +++ b/framework/src/android/net/StaticIpConfiguration.aidl @@ -0,0 +1,20 @@ +/* +** +** 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; + +@JavaOnlyStableParcelable parcelable StaticIpConfiguration; \ No newline at end of file diff --git a/framework/src/android/net/StaticIpConfiguration.java b/framework/src/android/net/StaticIpConfiguration.java new file mode 100644 index 0000000000..ce545974f5 --- /dev/null +++ b/framework/src/android/net/StaticIpConfiguration.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2014 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; +import com.android.net.module.util.InetAddressUtils; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Class that describes static IP configuration. + * + *

This class is different from {@link LinkProperties} because it represents + * configuration intent. The general contract is that if we can represent + * a configuration here, then we should be able to configure it on a network. + * The intent is that it closely match the UI we have for configuring networks. + * + *

In contrast, {@link LinkProperties} represents current state. It is much more + * expressive. For example, it supports multiple IP addresses, multiple routes, + * stacked interfaces, and so on. Because LinkProperties is so expressive, + * using it to represent configuration intent as well as current state causes + * problems. For example, we could unknowingly save a configuration that we are + * not in fact capable of applying, or we could save a configuration that the + * UI cannot display, which has the potential for malicious code to hide + * hostile or unexpected configuration from the user. + * + * @hide + */ +@SystemApi +public final class StaticIpConfiguration implements Parcelable { + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Nullable + public LinkAddress ipAddress; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Nullable + public InetAddress gateway; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @NonNull + public final ArrayList dnsServers; + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Nullable + public String domains; + + public StaticIpConfiguration() { + dnsServers = new ArrayList<>(); + } + + public StaticIpConfiguration(@Nullable StaticIpConfiguration source) { + this(); + if (source != null) { + // All of these except dnsServers are immutable, so no need to make copies. + ipAddress = source.ipAddress; + gateway = source.gateway; + dnsServers.addAll(source.dnsServers); + domains = source.domains; + } + } + + public void clear() { + ipAddress = null; + gateway = null; + dnsServers.clear(); + domains = null; + } + + /** + * Get the static IP address included in the configuration. + */ + public @Nullable LinkAddress getIpAddress() { + return ipAddress; + } + + /** + * Get the gateway included in the configuration. + */ + public @Nullable InetAddress getGateway() { + return gateway; + } + + /** + * Get the DNS servers included in the configuration. + */ + public @NonNull List getDnsServers() { + return dnsServers; + } + + /** + * Get a {@link String} containing the comma separated domains to search when resolving host + * names on this link, in priority order. + */ + public @Nullable String getDomains() { + return domains; + } + + /** + * Helper class to build a new instance of {@link StaticIpConfiguration}. + */ + public static final class Builder { + private LinkAddress mIpAddress; + private InetAddress mGateway; + private Iterable mDnsServers; + private String mDomains; + + /** + * Set the IP address to be included in the configuration; null by default. + * @return The {@link Builder} for chaining. + */ + public @NonNull Builder setIpAddress(@Nullable LinkAddress ipAddress) { + mIpAddress = ipAddress; + return this; + } + + /** + * Set the address of the gateway to be included in the configuration; null by default. + * @return The {@link Builder} for chaining. + */ + public @NonNull Builder setGateway(@Nullable InetAddress gateway) { + mGateway = gateway; + return this; + } + + /** + * Set the addresses of the DNS servers included in the configuration; empty by default. + * @return The {@link Builder} for chaining. + */ + public @NonNull Builder setDnsServers(@NonNull Iterable dnsServers) { + Preconditions.checkNotNull(dnsServers); + mDnsServers = dnsServers; + return this; + } + + /** + * Sets the DNS domain search path to be used on the link; null by default. + * @param newDomains A {@link String} containing the comma separated domains to search when + * resolving host names on this link, in priority order. + * @return The {@link Builder} for chaining. + */ + public @NonNull Builder setDomains(@Nullable String newDomains) { + mDomains = newDomains; + return this; + } + + /** + * Create a {@link StaticIpConfiguration} from the parameters in this {@link Builder}. + * @return The newly created StaticIpConfiguration. + */ + public @NonNull StaticIpConfiguration build() { + final StaticIpConfiguration config = new StaticIpConfiguration(); + config.ipAddress = mIpAddress; + config.gateway = mGateway; + if (mDnsServers != null) { + for (InetAddress server : mDnsServers) { + config.dnsServers.add(server); + } + } + config.domains = mDomains; + return config; + } + } + + /** + * Add a DNS server to this configuration. + */ + public void addDnsServer(@NonNull InetAddress server) { + dnsServers.add(server); + } + + /** + * Returns the network routes specified by this object. Will typically include a + * directly-connected route for the IP address's local subnet and a default route. + * @param iface Interface to include in the routes. + */ + public @NonNull List getRoutes(@Nullable String iface) { + List routes = new ArrayList(3); + if (ipAddress != null) { + RouteInfo connectedRoute = new RouteInfo(ipAddress, null, iface); + routes.add(connectedRoute); + // If the default gateway is not covered by the directly-connected route, also add a + // host route to the gateway as well. This configuration is arguably invalid, but it + // used to work in K and earlier, and other OSes appear to accept it. + if (gateway != null && !connectedRoute.matches(gateway)) { + routes.add(RouteInfo.makeHostRoute(gateway, iface)); + } + } + if (gateway != null) { + routes.add(new RouteInfo((IpPrefix) null, gateway, iface)); + } + return routes; + } + + /** + * Returns a LinkProperties object expressing the data in this object. Note that the information + * contained in the LinkProperties will not be a complete picture of the link's configuration, + * because any configuration information that is obtained dynamically by the network (e.g., + * IPv6 configuration) will not be included. + * @hide + */ + public @NonNull LinkProperties toLinkProperties(String iface) { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(iface); + if (ipAddress != null) { + lp.addLinkAddress(ipAddress); + } + for (RouteInfo route : getRoutes(iface)) { + lp.addRoute(route); + } + for (InetAddress dns : dnsServers) { + lp.addDnsServer(dns); + } + lp.setDomains(domains); + return lp; + } + + @NonNull + @Override + public String toString() { + StringBuffer str = new StringBuffer(); + + str.append("IP address "); + if (ipAddress != null ) str.append(ipAddress).append(" "); + + str.append("Gateway "); + if (gateway != null) str.append(gateway.getHostAddress()).append(" "); + + str.append(" DNS servers: ["); + for (InetAddress dnsServer : dnsServers) { + str.append(" ").append(dnsServer.getHostAddress()); + } + + str.append(" ] Domains "); + if (domains != null) str.append(domains); + return str.toString(); + } + + @Override + public int hashCode() { + int result = 13; + result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode()); + result = 47 * result + (gateway == null ? 0 : gateway.hashCode()); + result = 47 * result + (domains == null ? 0 : domains.hashCode()); + result = 47 * result + dnsServers.hashCode(); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + + if (!(obj instanceof StaticIpConfiguration)) return false; + + StaticIpConfiguration other = (StaticIpConfiguration) obj; + + return other != null && + Objects.equals(ipAddress, other.ipAddress) && + Objects.equals(gateway, other.gateway) && + dnsServers.equals(other.dnsServers) && + Objects.equals(domains, other.domains); + } + + /** Implement the Parcelable interface */ + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public StaticIpConfiguration createFromParcel(Parcel in) { + return readFromParcel(in); + } + + public StaticIpConfiguration[] newArray(int size) { + return new StaticIpConfiguration[size]; + } + }; + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(ipAddress, flags); + InetAddressUtils.parcelInetAddress(dest, gateway, flags); + dest.writeInt(dnsServers.size()); + for (InetAddress dnsServer : dnsServers) { + InetAddressUtils.parcelInetAddress(dest, dnsServer, flags); + } + dest.writeString(domains); + } + + /** @hide */ + public static StaticIpConfiguration readFromParcel(Parcel in) { + final StaticIpConfiguration s = new StaticIpConfiguration(); + s.ipAddress = in.readParcelable(null); + s.gateway = InetAddressUtils.unparcelInetAddress(in); + s.dnsServers.clear(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + s.dnsServers.add(InetAddressUtils.unparcelInetAddress(in)); + } + s.domains = in.readString(); + return s; + } +} diff --git a/framework/src/android/net/TcpKeepalivePacketData.java b/framework/src/android/net/TcpKeepalivePacketData.java new file mode 100644 index 0000000000..ddb3a6a72f --- /dev/null +++ b/framework/src/android/net/TcpKeepalivePacketData.java @@ -0,0 +1,163 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.net.InetAddress; +import java.util.Objects; + +/** + * Represents the actual tcp keep alive packets which will be used for hardware offload. + * @hide + */ +@SystemApi +public final class TcpKeepalivePacketData extends KeepalivePacketData implements Parcelable { + private static final String TAG = "TcpKeepalivePacketData"; + + /** TCP sequence number. */ + public final int tcpSeq; + + /** TCP ACK number. */ + public final int tcpAck; + + /** TCP RCV window. */ + public final int tcpWindow; + + /** TCP RCV window scale. */ + public final int tcpWindowScale; + + /** IP TOS. */ + public final int ipTos; + + /** IP TTL. */ + public final int ipTtl; + + public TcpKeepalivePacketData(@NonNull final InetAddress srcAddress, int srcPort, + @NonNull final InetAddress dstAddress, int dstPort, @NonNull final byte[] data, + int tcpSeq, int tcpAck, int tcpWindow, int tcpWindowScale, int ipTos, int ipTtl) + throws InvalidPacketException { + super(srcAddress, srcPort, dstAddress, dstPort, data); + this.tcpSeq = tcpSeq; + this.tcpAck = tcpAck; + this.tcpWindow = tcpWindow; + this.tcpWindowScale = tcpWindowScale; + this.ipTos = ipTos; + this.ipTtl = ipTtl; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (!(o instanceof TcpKeepalivePacketData)) return false; + final TcpKeepalivePacketData other = (TcpKeepalivePacketData) o; + final InetAddress srcAddress = getSrcAddress(); + final InetAddress dstAddress = getDstAddress(); + return srcAddress.equals(other.getSrcAddress()) + && dstAddress.equals(other.getDstAddress()) + && getSrcPort() == other.getSrcPort() + && getDstPort() == other.getDstPort() + && this.tcpAck == other.tcpAck + && this.tcpSeq == other.tcpSeq + && this.tcpWindow == other.tcpWindow + && this.tcpWindowScale == other.tcpWindowScale + && this.ipTos == other.ipTos + && this.ipTtl == other.ipTtl; + } + + @Override + public int hashCode() { + return Objects.hash(getSrcAddress(), getDstAddress(), getSrcPort(), getDstPort(), + tcpAck, tcpSeq, tcpWindow, tcpWindowScale, ipTos, ipTtl); + } + + /** + * Parcelable Implementation. + * Note that this object implements parcelable (and needs to keep doing this as it inherits + * from a class that does), but should usually be parceled as a stable parcelable using + * the toStableParcelable() and fromStableParcelable() methods. + */ + @Override + public int describeContents() { + return 0; + } + + /** Write to parcel. */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(getSrcAddress().getHostAddress()); + out.writeString(getDstAddress().getHostAddress()); + out.writeInt(getSrcPort()); + out.writeInt(getDstPort()); + out.writeByteArray(getPacket()); + out.writeInt(tcpSeq); + out.writeInt(tcpAck); + out.writeInt(tcpWindow); + out.writeInt(tcpWindowScale); + out.writeInt(ipTos); + out.writeInt(ipTtl); + } + + private static TcpKeepalivePacketData readFromParcel(Parcel in) throws InvalidPacketException { + InetAddress srcAddress = InetAddresses.parseNumericAddress(in.readString()); + InetAddress dstAddress = InetAddresses.parseNumericAddress(in.readString()); + int srcPort = in.readInt(); + int dstPort = in.readInt(); + byte[] packet = in.createByteArray(); + int tcpSeq = in.readInt(); + int tcpAck = in.readInt(); + int tcpWnd = in.readInt(); + int tcpWndScale = in.readInt(); + int ipTos = in.readInt(); + int ipTtl = in.readInt(); + return new TcpKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, packet, tcpSeq, + tcpAck, tcpWnd, tcpWndScale, ipTos, ipTtl); + } + + /** Parcelable Creator. */ + public static final @NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public TcpKeepalivePacketData createFromParcel(Parcel in) { + try { + return readFromParcel(in); + } catch (InvalidPacketException e) { + throw new IllegalArgumentException( + "Invalid TCP keepalive data: " + e.getError()); + } + } + + public TcpKeepalivePacketData[] newArray(int size) { + return new TcpKeepalivePacketData[size]; + } + }; + + @Override + public String toString() { + return "saddr: " + getSrcAddress() + + " daddr: " + getDstAddress() + + " sport: " + getSrcPort() + + " dport: " + getDstPort() + + " seq: " + tcpSeq + + " ack: " + tcpAck + + " window: " + tcpWindow + + " windowScale: " + tcpWindowScale + + " tos: " + ipTos + + " ttl: " + ipTtl; + } +} diff --git a/framework/src/android/net/TcpRepairWindow.java b/framework/src/android/net/TcpRepairWindow.java new file mode 100644 index 0000000000..f062fa9034 --- /dev/null +++ b/framework/src/android/net/TcpRepairWindow.java @@ -0,0 +1,48 @@ +/* + * 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; + +import android.annotation.SystemApi; + +/** + * Corresponds to C's {@code struct tcp_repair_window} from + * include/uapi/linux/tcp.h + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TcpRepairWindow { + public final int sndWl1; + public final int sndWnd; + public final int maxWindow; + public final int rcvWnd; + public final int rcvWup; + public final int rcvWndScale; + + /** + * Constructs an instance with the given field values. + */ + public TcpRepairWindow(final int sndWl1, final int sndWnd, final int maxWindow, + final int rcvWnd, final int rcvWup, final int rcvWndScale) { + this.sndWl1 = sndWl1; + this.sndWnd = sndWnd; + this.maxWindow = maxWindow; + this.rcvWnd = rcvWnd; + this.rcvWup = rcvWup; + this.rcvWndScale = rcvWndScale; + } +} diff --git a/framework/src/android/net/TcpSocketKeepalive.java b/framework/src/android/net/TcpSocketKeepalive.java new file mode 100644 index 0000000000..d89814d49b --- /dev/null +++ b/framework/src/android/net/TcpSocketKeepalive.java @@ -0,0 +1,77 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; + +import java.util.concurrent.Executor; + +/** @hide */ +final class TcpSocketKeepalive extends SocketKeepalive { + + TcpSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull ParcelFileDescriptor pfd, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, pfd, executor, callback); + } + + /** + * Starts keepalives. {@code mSocket} must be a connected TCP socket. + * + * - The application must not write to or read from the socket after calling this method, until + * onDataReceived, onStopped, or onError are called. If it does, the keepalive will fail + * with {@link #ERROR_SOCKET_NOT_IDLE}, or {@code #ERROR_INVALID_SOCKET} if the socket + * experienced an error (as in poll(2) returned POLLERR or POLLHUP); if this happens, the data + * received from the socket may be invalid, and the socket can't be recovered. + * - If the socket has data in the send or receive buffer, then this call will fail with + * {@link #ERROR_SOCKET_NOT_IDLE} and can be retried after the data has been processed. + * An app could ensure this by using an application-layer protocol to receive acknowledgement + * that indicates all data has been delivered to server, e.g. HTTP 200 OK. + * Then the app could go into keepalive mode after reading all remaining data within the + * acknowledgement. + */ + @Override + void startImpl(int intervalSec) { + mExecutor.execute(() -> { + try { + mService.startTcpKeepalive(mNetwork, mPfd, intervalSec, mCallback); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } + + @Override + void stopImpl() { + mExecutor.execute(() -> { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + throw e.rethrowFromSystemServer(); + } + }); + } +} diff --git a/framework/src/android/net/TestNetworkInterface.aidl b/framework/src/android/net/TestNetworkInterface.aidl new file mode 100644 index 0000000000..e1f4f9f794 --- /dev/null +++ b/framework/src/android/net/TestNetworkInterface.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +/** @hide */ +parcelable TestNetworkInterface; diff --git a/framework/src/android/net/TestNetworkInterface.java b/framework/src/android/net/TestNetworkInterface.java new file mode 100644 index 0000000000..4449ff8018 --- /dev/null +++ b/framework/src/android/net/TestNetworkInterface.java @@ -0,0 +1,78 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +/** + * This class is used to return the interface name and fd of the test interface + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TestNetworkInterface implements Parcelable { + @NonNull + private final ParcelFileDescriptor mFileDescriptor; + @NonNull + private final String mInterfaceName; + + @Override + public int describeContents() { + return (mFileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeParcelable(mFileDescriptor, PARCELABLE_WRITE_RETURN_VALUE); + out.writeString(mInterfaceName); + } + + public TestNetworkInterface(@NonNull ParcelFileDescriptor pfd, @NonNull String intf) { + mFileDescriptor = pfd; + mInterfaceName = intf; + } + + private TestNetworkInterface(@NonNull Parcel in) { + mFileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + mInterfaceName = in.readString(); + } + + @NonNull + public ParcelFileDescriptor getFileDescriptor() { + return mFileDescriptor; + } + + @NonNull + public String getInterfaceName() { + return mInterfaceName; + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public TestNetworkInterface createFromParcel(Parcel in) { + return new TestNetworkInterface(in); + } + + public TestNetworkInterface[] newArray(int size) { + return new TestNetworkInterface[size]; + } + }; +} diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java new file mode 100644 index 0000000000..4e894143bf --- /dev/null +++ b/framework/src/android/net/TestNetworkManager.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2018 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.internal.util.Preconditions; + +import java.util.Arrays; +import java.util.Collection; + +/** + * Class that allows creation and management of per-app, test-only networks + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public class TestNetworkManager { + /** + * Prefix for tun interfaces created by this class. + * @hide + */ + public static final String TEST_TUN_PREFIX = "testtun"; + + /** + * Prefix for tap interfaces created by this class. + * @hide + */ + public static final String TEST_TAP_PREFIX = "testtap"; + + @NonNull private static final String TAG = TestNetworkManager.class.getSimpleName(); + + @NonNull private final ITestNetworkManager mService; + + /** @hide */ + public TestNetworkManager(@NonNull ITestNetworkManager service) { + mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager"); + } + + /** + * Teardown the capability-limited, testing-only network for a given interface + * + * @param network The test network that should be torn down + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void teardownTestNetwork(@NonNull Network network) { + try { + mService.teardownTestNetwork(network.netId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void setupTestNetwork( + @NonNull String iface, + @Nullable LinkProperties lp, + boolean isMetered, + @NonNull int[] administratorUids, + @NonNull IBinder binder) { + try { + mService.setupTestNetwork(iface, lp, isMetered, administratorUids, binder); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets up a capability-limited, testing-only network for a given interface + * + * @param lp The LinkProperties for the TestNetworkService to use for this test network. Note + * that the interface name and link addresses will be overwritten, and the passed-in values + * discarded. + * @param isMetered Whether or not the network should be considered metered. + * @param binder A binder object guarding the lifecycle of this test network. + * @hide + */ + public void setupTestNetwork( + @NonNull LinkProperties lp, boolean isMetered, @NonNull IBinder binder) { + Preconditions.checkNotNull(lp, "Invalid LinkProperties"); + setupTestNetwork(lp.getInterfaceName(), lp, isMetered, new int[0], binder); + } + + /** + * Sets up a capability-limited, testing-only network for a given interface + * + * @param iface the name of the interface to be used for the Network LinkProperties. + * @param binder A binder object guarding the lifecycle of this test network. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) { + setupTestNetwork(iface, null, true, new int[0], binder); + } + + /** + * Sets up a capability-limited, testing-only network for a given interface with the given + * administrator UIDs. + * + * @param iface the name of the interface to be used for the Network LinkProperties. + * @param administratorUids The administrator UIDs to be used for the test-only network + * @param binder A binder object guarding the lifecycle of this test network. + * @hide + */ + public void setupTestNetwork( + @NonNull String iface, @NonNull int[] administratorUids, @NonNull IBinder binder) { + setupTestNetwork(iface, null, true, administratorUids, binder); + } + + /** + * Create a tun interface for testing purposes + * + * @param linkAddrs an array of LinkAddresses to assign to the TUN interface + * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the + * TUN interface. + * @deprecated Use {@link #createTunInterface(Collection)} instead. + * @hide + */ + @Deprecated + @NonNull + public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) { + return createTunInterface(Arrays.asList(linkAddrs)); + } + + /** + * Create a tun interface for testing purposes + * + * @param linkAddrs an array of LinkAddresses to assign to the TUN interface + * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the + * TUN interface. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull + public TestNetworkInterface createTunInterface(@NonNull Collection linkAddrs) { + try { + final LinkAddress[] arr = new LinkAddress[linkAddrs.size()]; + return mService.createTunInterface(linkAddrs.toArray(arr)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Create a tap interface for testing purposes + * + * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the + * TAP interface. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @NonNull + public TestNetworkInterface createTapInterface() { + try { + return mService.createTapInterface(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + +} diff --git a/framework/src/android/net/TransportInfo.java b/framework/src/android/net/TransportInfo.java new file mode 100644 index 0000000000..aa4bbb0511 --- /dev/null +++ b/framework/src/android/net/TransportInfo.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 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; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +/** + * A container for transport-specific capabilities which is returned by + * {@link NetworkCapabilities#getTransportInfo()}. Specific networks + * may provide concrete implementations of this interface. + * @see android.net.wifi.aware.WifiAwareNetworkInfo + * @see android.net.wifi.WifiInfo + */ +public interface TransportInfo { + + /** + * Create a copy of a {@link TransportInfo} that will preserve location sensitive fields that + * were set based on the permissions of the process that originally received it. + * + *

By default {@link TransportInfo} does not preserve such fields during parceling, as + * they should not be shared outside of the process that receives them without appropriate + * checks. + * + * @param parcelLocationSensitiveFields Whether the location sensitive fields should be kept + * when parceling + * @return Copy of this instance. + * @hide + */ + @SystemApi + @NonNull + default TransportInfo makeCopy(boolean parcelLocationSensitiveFields) { + return this; + } + + /** + * Returns whether this TransportInfo type has location sensitive fields or not (helps + * to determine whether to perform a location permission check or not before sending to + * apps). + * + * @return {@code true} if this instance contains location sensitive info, {@code false} + * otherwise. + * @hide + */ + @SystemApi + default boolean hasLocationSensitiveFields() { + return false; + } +} diff --git a/framework/src/android/net/UidRange.aidl b/framework/src/android/net/UidRange.aidl new file mode 100644 index 0000000000..f70fc8e2fe --- /dev/null +++ b/framework/src/android/net/UidRange.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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; + +/** + * An inclusive range of UIDs. + * + * {@hide} + */ +parcelable UidRange; \ No newline at end of file diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java new file mode 100644 index 0000000000..c87b8279c4 --- /dev/null +++ b/framework/src/android/net/VpnManager.java @@ -0,0 +1,164 @@ +/* + * 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; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.RemoteException; + +import com.android.internal.net.VpnProfile; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.GeneralSecurityException; + +/** + * This class provides an interface for apps to manage platform VPN profiles + * + *

Apps can use this API to provide profiles with which the platform can set up a VPN without + * further app intermediation. When a VPN profile is present and the app is selected as an always-on + * VPN, the platform will directly trigger the negotiation of the VPN without starting or waking the + * app (unlike VpnService). + * + *

VPN apps using supported protocols should preferentially use this API over the {@link + * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user + * the guarantee that VPN network traffic is not subjected to on-device packet interception. + * + * @see Ikev2VpnProfile + */ +public class VpnManager { + /** Type representing a lack of VPN @hide */ + public static final int TYPE_VPN_NONE = -1; + /** VPN service type code @hide */ + public static final int TYPE_VPN_SERVICE = 1; + /** Platform VPN type code @hide */ + public static final int TYPE_VPN_PLATFORM = 2; + + /** @hide */ + @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM}) + @Retention(RetentionPolicy.SOURCE) + public @interface VpnType {} + + @NonNull private final Context mContext; + @NonNull private final IConnectivityManager mService; + + private static Intent getIntentForConfirmation() { + final Intent intent = new Intent(); + final ComponentName componentName = ComponentName.unflattenFromString( + Resources.getSystem().getString( + com.android.internal.R.string.config_platformVpnConfirmDialogComponent)); + intent.setComponent(componentName); + return intent; + } + + /** + * Create an instance of the VpnManager with the given context. + * + *

Internal only. Applications are expected to obtain an instance of the VpnManager via the + * {@link Context.getSystemService()} method call. + * + * @hide + */ + public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) { + mContext = checkNotNull(ctx, "missing Context"); + mService = checkNotNull(service, "missing IConnectivityManager"); + } + + /** + * Install a VpnProfile configuration keyed on the calling app's package name. + * + *

This method returns {@code null} if user consent has already been granted, or an {@link + * Intent} to a system activity. If an intent is returned, the application should launch the + * activity using {@link Activity#startActivityForResult} to request user consent. The activity + * may pop up a dialog to require user action, and the result will come back via its {@link + * Activity#onActivityResult}. If the result is {@link Activity#RESULT_OK}, the user has + * consented, and the VPN profile can be started. + * + * @param profile the VpnProfile provided by this package. Will override any previous VpnProfile + * stored for this package. + * @return an Intent requesting user consent to start the VPN, or null if consent is not + * required based on privileges or previous user consent. + */ + @Nullable + public Intent provisionVpnProfile(@NonNull PlatformVpnProfile profile) { + final VpnProfile internalProfile; + + try { + internalProfile = profile.toVpnProfile(); + } catch (GeneralSecurityException | IOException e) { + // Conversion to VpnProfile failed; this is an invalid profile. Both of these exceptions + // indicate a failure to convert a PrivateKey or X509Certificate to a Base64 encoded + // string as required by the VpnProfile. + throw new IllegalArgumentException("Failed to serialize PlatformVpnProfile", e); + } + + try { + // Profile can never be null; it either gets set, or an exception is thrown. + if (mService.provisionVpnProfile(internalProfile, mContext.getOpPackageName())) { + return null; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return getIntentForConfirmation(); + } + + /** + * Delete the VPN profile configuration that was provisioned by the calling app + * + * @throws SecurityException if this would violate user settings + */ + public void deleteProvisionedVpnProfile() { + try { + mService.deleteVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request the startup of a previously provisioned VPN. + * + * @throws SecurityException exception if user or device settings prevent this VPN from being + * setup, or if user consent has not been granted + */ + public void startProvisionedVpnProfile() { + try { + mService.startVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Tear down the VPN provided by the calling app (if any) */ + public void stopProvisionedVpnProfile() { + try { + mService.stopVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/framework/src/android/net/VpnService.java b/framework/src/android/net/VpnService.java new file mode 100644 index 0000000000..8e90a119fe --- /dev/null +++ b/framework/src/android/net/VpnService.java @@ -0,0 +1,903 @@ +/* + * Copyright (C) 2011 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; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.app.Activity; +import android.app.PendingIntent; +import android.app.Service; +import android.app.admin.DevicePolicyManager; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; + +import com.android.internal.net.VpnConfig; + +import java.net.DatagramSocket; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * VpnService is a base class for applications to extend and build their + * own VPN solutions. In general, it creates a virtual network interface, + * configures addresses and routing rules, and returns a file descriptor + * to the application. Each read from the descriptor retrieves an outgoing + * packet which was routed to the interface. Each write to the descriptor + * injects an incoming packet just like it was received from the interface. + * The interface is running on Internet Protocol (IP), so packets are + * always started with IP headers. The application then completes a VPN + * connection by processing and exchanging packets with the remote server + * over a tunnel. + * + *

Letting applications intercept packets raises huge security concerns. + * A VPN application can easily break the network. Besides, two of them may + * conflict with each other. The system takes several actions to address + * these issues. Here are some key points: + *

    + *
  • User action is required the first time an application creates a VPN + * connection.
  • + *
  • There can be only one VPN connection running at the same time. The + * existing interface is deactivated when a new one is created.
  • + *
  • A system-managed notification is shown during the lifetime of a + * VPN connection.
  • + *
  • A system-managed dialog gives the information of the current VPN + * connection. It also provides a button to disconnect.
  • + *
  • The network is restored automatically when the file descriptor is + * closed. It also covers the cases when a VPN application is crashed + * or killed by the system.
  • + *
+ * + *

There are two primary methods in this class: {@link #prepare} and + * {@link Builder#establish}. The former deals with user action and stops + * the VPN connection created by another application. The latter creates + * a VPN interface using the parameters supplied to the {@link Builder}. + * An application must call {@link #prepare} to grant the right to use + * other methods in this class, and the right can be revoked at any time. + * Here are the general steps to create a VPN connection: + *

    + *
  1. When the user presses the button to connect, call {@link #prepare} + * and launch the returned intent, if non-null.
  2. + *
  3. When the application becomes prepared, start the service.
  4. + *
  5. Create a tunnel to the remote server and negotiate the network + * parameters for the VPN connection.
  6. + *
  7. Supply those parameters to a {@link Builder} and create a VPN + * interface by calling {@link Builder#establish}.
  8. + *
  9. Process and exchange packets between the tunnel and the returned + * file descriptor.
  10. + *
  11. When {@link #onRevoke} is invoked, close the file descriptor and + * shut down the tunnel gracefully.
  12. + *
+ * + *

Services extending this class need to be declared with an appropriate + * permission and intent filter. Their access must be secured by + * {@link android.Manifest.permission#BIND_VPN_SERVICE} permission, and + * their intent filter must match {@link #SERVICE_INTERFACE} action. Here + * is an example of declaring a VPN service in {@code AndroidManifest.xml}: + *

+ * <service android:name=".ExampleVpnService"
+ *         android:permission="android.permission.BIND_VPN_SERVICE">
+ *     <intent-filter>
+ *         <action android:name="android.net.VpnService"/>
+ *     </intent-filter>
+ * </service>
+ * + *

The Android system starts a VPN in the background by calling + * {@link android.content.Context#startService startService()}. In Android 8.0 + * (API level 26) and higher, the system places VPN apps on the temporary + * allowlist for a short period so the app can start in the background. The VPN + * app must promote itself to the foreground after it's launched or the system + * will shut down the app. + * + *

Developer's guide

+ * + *

To learn more about developing VPN apps, read the + * VPN developer's guide. + * + * @see Builder + */ +public class VpnService extends Service { + + /** + * The action must be matched by the intent filter of this service. It also + * needs to require {@link android.Manifest.permission#BIND_VPN_SERVICE} + * permission so that other applications cannot abuse it. + */ + public static final String SERVICE_INTERFACE = VpnConfig.SERVICE_INTERFACE; + + /** + * Key for boolean meta-data field indicating whether this VpnService supports always-on mode. + * + *

For a VPN app targeting {@link android.os.Build.VERSION_CODES#N API 24} or above, Android + * provides users with the ability to set it as always-on, so that VPN connection is + * persisted after device reboot and app upgrade. Always-on VPN can also be enabled by device + * owner and profile owner apps through + * {@link DevicePolicyManager#setAlwaysOnVpnPackage}. + * + *

VPN apps not supporting this feature should opt out by adding this meta-data field to the + * {@code VpnService} component of {@code AndroidManifest.xml}. In case there is more than one + * {@code VpnService} component defined in {@code AndroidManifest.xml}, opting out any one of + * them will opt out the entire app. For example, + *

 {@code
+     * 
+     *     
+     *         
+     *     
+     *     
+     * 
+     * } 
+ * + *

This meta-data field defaults to {@code true} if absent. It will only have effect on + * {@link android.os.Build.VERSION_CODES#O_MR1} or higher. + */ + public static final String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = + "android.net.VpnService.SUPPORTS_ALWAYS_ON"; + + /** + * Use IConnectivityManager since those methods are hidden and not + * available in ConnectivityManager. + */ + private static IConnectivityManager getService() { + return IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + } + + /** + * Prepare to establish a VPN connection. This method returns {@code null} + * if the VPN application is already prepared or if the user has previously + * consented to the VPN application. Otherwise, it returns an + * {@link Intent} to a system activity. The application should launch the + * activity using {@link Activity#startActivityForResult} to get itself + * prepared. The activity may pop up a dialog to require user action, and + * the result will come back via its {@link Activity#onActivityResult}. + * If the result is {@link Activity#RESULT_OK}, the application becomes + * prepared and is granted to use other methods in this class. + * + *

Only one application can be granted at the same time. The right + * is revoked when another application is granted. The application + * losing the right will be notified via its {@link #onRevoke}. Unless + * it becomes prepared again, subsequent calls to other methods in this + * class will fail. + * + *

The user may disable the VPN at any time while it is activated, in + * which case this method will return an intent the next time it is + * executed to obtain the user's consent again. + * + * @see #onRevoke + */ + public static Intent prepare(Context context) { + try { + if (getService().prepareVpn(context.getPackageName(), null, context.getUserId())) { + return null; + } + } catch (RemoteException e) { + // ignore + } + return VpnConfig.getIntentForConfirmation(); + } + + /** + * Version of {@link #prepare(Context)} which does not require user consent. + * + *

Requires {@link android.Manifest.permission#CONTROL_VPN} and should generally not be + * used. Only acceptable in situations where user consent has been obtained through other means. + * + *

Once this is run, future preparations may be done with the standard prepare method as this + * will authorize the package to prepare the VPN without consent in the future. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CONTROL_VPN) + public static void prepareAndAuthorize(Context context) { + IConnectivityManager cm = getService(); + String packageName = context.getPackageName(); + try { + // Only prepare if we're not already prepared. + int userId = context.getUserId(); + if (!cm.prepareVpn(packageName, null, userId)) { + cm.prepareVpn(null, packageName, userId); + } + cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); + } catch (RemoteException e) { + // ignore + } + } + + /** + * Protect a socket from VPN connections. After protecting, data sent + * through this socket will go directly to the underlying network, + * so its traffic will not be forwarded through the VPN. + * This method is useful if some connections need to be kept + * outside of VPN. For example, a VPN tunnel should protect itself if its + * destination is covered by VPN routes. Otherwise its outgoing packets + * will be sent back to the VPN interface and cause an infinite loop. This + * method will fail if the application is not prepared or is revoked. + * + *

The socket is NOT closed by this method. + * + * @return {@code true} on success. + */ + public boolean protect(int socket) { + return NetworkUtils.protectFromVpn(socket); + } + + /** + * Convenience method to protect a {@link Socket} from VPN connections. + * + * @return {@code true} on success. + * @see #protect(int) + */ + public boolean protect(Socket socket) { + return protect(socket.getFileDescriptor$().getInt$()); + } + + /** + * Convenience method to protect a {@link DatagramSocket} from VPN + * connections. + * + * @return {@code true} on success. + * @see #protect(int) + */ + public boolean protect(DatagramSocket socket) { + return protect(socket.getFileDescriptor$().getInt$()); + } + + /** + * Adds a network address to the VPN interface. + * + * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the + * address is already in use or cannot be assigned to the interface for any other reason. + * + * Adding an address implicitly allows traffic from that address family (i.e., IPv4 or IPv6) to + * be routed over the VPN. @see Builder#allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + * + * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. + * @param prefixLength The prefix length of the address. + * + * @return {@code true} on success. + * @see Builder#addAddress + * + * @hide + */ + public boolean addAddress(InetAddress address, int prefixLength) { + check(address, prefixLength); + try { + return getService().addVpnAddress(address.getHostAddress(), prefixLength); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Removes a network address from the VPN interface. + * + * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the + * address is not assigned to the VPN interface, or if it is the only address assigned (thus + * cannot be removed), or if the address cannot be removed for any other reason. + * + * After removing an address, if there are no addresses, routes or DNS servers of a particular + * address family (i.e., IPv4 or IPv6) configured on the VPN, that DOES NOT block that + * family from being routed. In other words, once an address family has been allowed, it stays + * allowed for the rest of the VPN's session. @see Builder#allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + * + * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. + * @param prefixLength The prefix length of the address. + * + * @return {@code true} on success. + * + * @hide + */ + public boolean removeAddress(InetAddress address, int prefixLength) { + check(address, prefixLength); + try { + return getService().removeVpnAddress(address.getHostAddress(), prefixLength); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Sets the underlying networks used by the VPN for its upstream connections. + * + *

Used by the system to know the actual networks that carry traffic for apps affected by + * this VPN in order to present this information to the user (e.g., via status bar icons). + * + *

This method only needs to be called if the VPN has explicitly bound its underlying + * communications channels — such as the socket(s) passed to {@link #protect(int)} — + * to a {@code Network} using APIs such as {@link Network#bindSocket(Socket)} or + * {@link Network#bindSocket(DatagramSocket)}. The VPN should call this method every time + * the set of {@code Network}s it is using changes. + * + *

{@code networks} is one of the following: + *

    + *
  • a non-empty array: an array of one or more {@link Network}s, in + * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular) + * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear + * first in the array.
  • + *
  • an empty array: a zero-element array, meaning that the VPN has no + * underlying network connection, and thus, app traffic will not be sent or received.
  • + *
  • null: (default) signifies that the VPN uses whatever is the system's + * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket} + * APIs mentioned above to send traffic over specific channels.
  • + *
+ * + *

This call will succeed only if the VPN is currently established. For setting this value + * when the VPN has not yet been established, see {@link Builder#setUnderlyingNetworks}. + * + * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. + * + * @return {@code true} on success. + */ + public boolean setUnderlyingNetworks(Network[] networks) { + try { + return getService().setUnderlyingNetworksForVpn(networks); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Returns whether the service is running in always-on VPN mode. In this mode the system ensures + * that the service is always running by restarting it when necessary, e.g. after reboot. + * + * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set) + */ + public final boolean isAlwaysOn() { + try { + return getService().isCallerCurrentAlwaysOnVpnApp(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns whether the service is running in always-on VPN lockdown mode. In this mode the + * system ensures that the service is always running and that the apps aren't allowed to bypass + * the VPN. + * + * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set) + */ + public final boolean isLockdownEnabled() { + try { + return getService().isCallerCurrentAlwaysOnVpnLockdownApp(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the communication interface to the service. This method returns + * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE} + * action. Applications overriding this method must identify the intent + * and return the corresponding interface accordingly. + * + * @see Service#onBind + */ + @Override + public IBinder onBind(Intent intent) { + if (intent != null && SERVICE_INTERFACE.equals(intent.getAction())) { + return new Callback(); + } + return null; + } + + /** + * Invoked when the application is revoked. At this moment, the VPN + * interface is already deactivated by the system. The application should + * close the file descriptor and shut down gracefully. The default + * implementation of this method is calling {@link Service#stopSelf()}. + * + *

Calls to this method may not happen on the main thread + * of the process. + * + * @see #prepare + */ + public void onRevoke() { + stopSelf(); + } + + /** + * Use raw Binder instead of AIDL since now there is only one usage. + */ + private class Callback extends Binder { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { + if (code == IBinder.LAST_CALL_TRANSACTION) { + onRevoke(); + return true; + } + return false; + } + } + + /** + * Private method to validate address and prefixLength. + */ + private static void check(InetAddress address, int prefixLength) { + if (address.isLoopbackAddress()) { + throw new IllegalArgumentException("Bad address"); + } + if (address instanceof Inet4Address) { + if (prefixLength < 0 || prefixLength > 32) { + throw new IllegalArgumentException("Bad prefixLength"); + } + } else if (address instanceof Inet6Address) { + if (prefixLength < 0 || prefixLength > 128) { + throw new IllegalArgumentException("Bad prefixLength"); + } + } else { + throw new IllegalArgumentException("Unsupported family"); + } + } + + /** + * Helper class to create a VPN interface. This class should be always + * used within the scope of the outer {@link VpnService}. + * + * @see VpnService + */ + public class Builder { + + private final VpnConfig mConfig = new VpnConfig(); + @UnsupportedAppUsage + private final List mAddresses = new ArrayList(); + @UnsupportedAppUsage + private final List mRoutes = new ArrayList(); + + public Builder() { + mConfig.user = VpnService.this.getClass().getName(); + } + + /** + * Set the name of this session. It will be displayed in + * system-managed dialogs and notifications. This is recommended + * not required. + */ + @NonNull + public Builder setSession(@NonNull String session) { + mConfig.session = session; + return this; + } + + /** + * Set the {@link PendingIntent} to an activity for users to + * configure the VPN connection. If it is not set, the button + * to configure will not be shown in system-managed dialogs. + */ + @NonNull + public Builder setConfigureIntent(@NonNull PendingIntent intent) { + mConfig.configureIntent = intent; + return this; + } + + /** + * Set the maximum transmission unit (MTU) of the VPN interface. If + * it is not set, the default value in the operating system will be + * used. + * + * @throws IllegalArgumentException if the value is not positive. + */ + @NonNull + public Builder setMtu(int mtu) { + if (mtu <= 0) { + throw new IllegalArgumentException("Bad mtu"); + } + mConfig.mtu = mtu; + return this; + } + + /** + * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation + * and it is possible that some apps will ignore it. + */ + @NonNull + public Builder setHttpProxy(@NonNull ProxyInfo proxyInfo) { + mConfig.proxyInfo = proxyInfo; + return this; + } + + /** + * Add a network address to the VPN interface. Both IPv4 and IPv6 + * addresses are supported. At least one address must be set before + * calling {@link #establish}. + * + * Adding an address implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + */ + @NonNull + public Builder addAddress(@NonNull InetAddress address, int prefixLength) { + check(address, prefixLength); + + if (address.isAnyLocalAddress()) { + throw new IllegalArgumentException("Bad address"); + } + mAddresses.add(new LinkAddress(address, prefixLength)); + mConfig.updateAllowedFamilies(address); + return this; + } + + /** + * Convenience method to add a network address to the VPN interface + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * Adding an address implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + * @see #addAddress(InetAddress, int) + */ + @NonNull + public Builder addAddress(@NonNull String address, int prefixLength) { + return addAddress(InetAddress.parseNumericAddress(address), prefixLength); + } + + /** + * Add a network route to the VPN interface. Both IPv4 and IPv6 + * routes are supported. + * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the route is invalid. + */ + @NonNull + public Builder addRoute(@NonNull InetAddress address, int prefixLength) { + check(address, prefixLength); + + int offset = prefixLength / 8; + byte[] bytes = address.getAddress(); + if (offset < bytes.length) { + for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) { + if (bytes[offset] != 0) { + throw new IllegalArgumentException("Bad address"); + } + } + } + mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null)); + mConfig.updateAllowedFamilies(address); + return this; + } + + /** + * Convenience method to add a network route to the VPN interface + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * Adding a route implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the route is invalid. + * @see #addRoute(InetAddress, int) + */ + @NonNull + public Builder addRoute(@NonNull String address, int prefixLength) { + return addRoute(InetAddress.parseNumericAddress(address), prefixLength); + } + + /** + * Add a DNS server to the VPN connection. Both IPv4 and IPv6 + * addresses are supported. If none is set, the DNS servers of + * the default network will be used. + * + * Adding a server implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + */ + @NonNull + public Builder addDnsServer(@NonNull InetAddress address) { + if (address.isLoopbackAddress() || address.isAnyLocalAddress()) { + throw new IllegalArgumentException("Bad address"); + } + if (mConfig.dnsServers == null) { + mConfig.dnsServers = new ArrayList(); + } + mConfig.dnsServers.add(address.getHostAddress()); + return this; + } + + /** + * Convenience method to add a DNS server to the VPN connection + * using a numeric address string. See {@link InetAddress} for the + * definitions of numeric address formats. + * + * Adding a server implicitly allows traffic from that address family + * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily + * + * @throws IllegalArgumentException if the address is invalid. + * @see #addDnsServer(InetAddress) + */ + @NonNull + public Builder addDnsServer(@NonNull String address) { + return addDnsServer(InetAddress.parseNumericAddress(address)); + } + + /** + * Add a search domain to the DNS resolver. + */ + @NonNull + public Builder addSearchDomain(@NonNull String domain) { + if (mConfig.searchDomains == null) { + mConfig.searchDomains = new ArrayList(); + } + mConfig.searchDomains.add(domain); + return this; + } + + /** + * Allows traffic from the specified address family. + * + * By default, if no address, route or DNS server of a specific family (IPv4 or IPv6) is + * added to this VPN, then all outgoing traffic of that family is blocked. If any address, + * route or DNS server is added, that family is allowed. + * + * This method allows an address family to be unblocked even without adding an address, + * route or DNS server of that family. Traffic of that family will then typically + * fall-through to the underlying network if it's supported. + * + * {@code family} must be either {@code AF_INET} (for IPv4) or {@code AF_INET6} (for IPv6). + * {@link IllegalArgumentException} is thrown if it's neither. + * + * @param family The address family ({@code AF_INET} or {@code AF_INET6}) to allow. + * + * @return this {@link Builder} object to facilitate chaining of method calls. + */ + @NonNull + public Builder allowFamily(int family) { + if (family == AF_INET) { + mConfig.allowIPv4 = true; + } else if (family == AF_INET6) { + mConfig.allowIPv6 = true; + } else { + throw new IllegalArgumentException(family + " is neither " + AF_INET + " nor " + + AF_INET6); + } + return this; + } + + private void verifyApp(String packageName) throws PackageManager.NameNotFoundException { + IPackageManager pm = IPackageManager.Stub.asInterface( + ServiceManager.getService("package")); + try { + pm.getApplicationInfo(packageName, 0, UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + + /** + * Adds an application that's allowed to access the VPN connection. + * + * If this method is called at least once, only applications added through this method (and + * no others) are allowed access. Else (if this method is never called), all applications + * are allowed by default. If some applications are added, other, un-added applications + * will use networking as if the VPN wasn't running. + * + * A {@link Builder} may have only a set of allowed applications OR a set of disallowed + * ones, but not both. Calling this method after {@link #addDisallowedApplication} has + * already been called, or vice versa, will throw an {@link UnsupportedOperationException}. + * + * {@code packageName} must be the canonical name of a currently installed application. + * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. + * + * @throws PackageManager.NameNotFoundException If the application isn't installed. + * + * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + @NonNull + public Builder addAllowedApplication(@NonNull String packageName) + throws PackageManager.NameNotFoundException { + if (mConfig.disallowedApplications != null) { + throw new UnsupportedOperationException("addDisallowedApplication already called"); + } + verifyApp(packageName); + if (mConfig.allowedApplications == null) { + mConfig.allowedApplications = new ArrayList(); + } + mConfig.allowedApplications.add(packageName); + return this; + } + + /** + * Adds an application that's denied access to the VPN connection. + * + * By default, all applications are allowed access, except for those denied through this + * method. Denied applications will use networking as if the VPN wasn't running. + * + * A {@link Builder} may have only a set of allowed applications OR a set of disallowed + * ones, but not both. Calling this method after {@link #addAllowedApplication} has already + * been called, or vice versa, will throw an {@link UnsupportedOperationException}. + * + * {@code packageName} must be the canonical name of a currently installed application. + * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. + * + * @throws PackageManager.NameNotFoundException If the application isn't installed. + * + * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + @NonNull + public Builder addDisallowedApplication(@NonNull String packageName) + throws PackageManager.NameNotFoundException { + if (mConfig.allowedApplications != null) { + throw new UnsupportedOperationException("addAllowedApplication already called"); + } + verifyApp(packageName); + if (mConfig.disallowedApplications == null) { + mConfig.disallowedApplications = new ArrayList(); + } + mConfig.disallowedApplications.add(packageName); + return this; + } + + /** + * Allows all apps to bypass this VPN connection. + * + * By default, all traffic from apps is forwarded through the VPN interface and it is not + * possible for apps to side-step the VPN. If this method is called, apps may use methods + * such as {@link ConnectivityManager#bindProcessToNetwork} to instead send/receive + * directly over the underlying network or any other network they have permissions for. + * + * @return this {@link Builder} object to facilitate chaining of method calls. + */ + @NonNull + public Builder allowBypass() { + mConfig.allowBypass = true; + return this; + } + + /** + * Sets the VPN interface's file descriptor to be in blocking/non-blocking mode. + * + * By default, the file descriptor returned by {@link #establish} is non-blocking. + * + * @param blocking True to put the descriptor into blocking mode; false for non-blocking. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + @NonNull + public Builder setBlocking(boolean blocking) { + mConfig.blocking = blocking; + return this; + } + + /** + * Sets the underlying networks used by the VPN for its upstream connections. + * + * @see VpnService#setUnderlyingNetworks + * + * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. + * + * @return this {@link Builder} object to facilitate chaining method calls. + */ + @NonNull + public Builder setUnderlyingNetworks(@Nullable Network[] networks) { + mConfig.underlyingNetworks = networks != null ? networks.clone() : null; + return this; + } + + /** + * Marks the VPN network as metered. A VPN network is classified as metered when the user is + * sensitive to heavy data usage due to monetary costs and/or data limitations. In such + * cases, you should set this to {@code true} so that apps on the system can avoid doing + * large data transfers. Otherwise, set this to {@code false}. Doing so would cause VPN + * network to inherit its meteredness from its underlying networks. + * + *

VPN apps targeting {@link android.os.Build.VERSION_CODES#Q} or above will be + * considered metered by default. + * + * @param isMetered {@code true} if VPN network should be treated as metered regardless of + * underlying network meteredness + * @return this {@link Builder} object to facilitate chaining method calls + * @see #setUnderlyingNetworks(Network[]) + * @see ConnectivityManager#isActiveNetworkMetered() + */ + @NonNull + public Builder setMetered(boolean isMetered) { + mConfig.isMetered = isMetered; + return this; + } + + /** + * Create a VPN interface using the parameters supplied to this + * builder. The interface works on IP packets, and a file descriptor + * is returned for the application to access them. Each read + * retrieves an outgoing packet which was routed to the interface. + * Each write injects an incoming packet just like it was received + * from the interface. The file descriptor is put into non-blocking + * mode by default to avoid blocking Java threads. To use the file + * descriptor completely in native space, see + * {@link ParcelFileDescriptor#detachFd()}. The application MUST + * close the file descriptor when the VPN connection is terminated. + * The VPN interface will be removed and the network will be + * restored by the system automatically. + * + *

To avoid conflicts, there can be only one active VPN interface + * at the same time. Usually network parameters are never changed + * during the lifetime of a VPN connection. It is also common for an + * application to create a new file descriptor after closing the + * previous one. However, it is rare but not impossible to have two + * interfaces while performing a seamless handover. In this case, the + * old interface will be deactivated when the new one is created + * successfully. Both file descriptors are valid but now outgoing + * packets will be routed to the new interface. Therefore, after + * draining the old file descriptor, the application MUST close it + * and start using the new file descriptor. If the new interface + * cannot be created, the existing interface and its file descriptor + * remain untouched. + * + *

An exception will be thrown if the interface cannot be created + * for any reason. However, this method returns {@code null} if the + * application is not prepared or is revoked. This helps solve + * possible race conditions between other VPN applications. + * + * @return {@link ParcelFileDescriptor} of the VPN interface, or + * {@code null} if the application is not prepared. + * @throws IllegalArgumentException if a parameter is not accepted + * by the operating system. + * @throws IllegalStateException if a parameter cannot be applied + * by the operating system. + * @throws SecurityException if the service is not properly declared + * in {@code AndroidManifest.xml}. + * @see VpnService + */ + @Nullable + public ParcelFileDescriptor establish() { + mConfig.addresses = mAddresses; + mConfig.routes = mRoutes; + + try { + return getService().establishVpn(mConfig); + } catch (RemoteException e) { + throw new IllegalStateException(e); + } + } + } +} diff --git a/framework/src/android/net/apf/ApfCapabilities.aidl b/framework/src/android/net/apf/ApfCapabilities.aidl new file mode 100644 index 0000000000..7c4d4c2da4 --- /dev/null +++ b/framework/src/android/net/apf/ApfCapabilities.aidl @@ -0,0 +1,20 @@ +/* +** +** 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.apf; + +@JavaOnlyStableParcelable parcelable ApfCapabilities; \ No newline at end of file diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java new file mode 100644 index 0000000000..bf5b26e278 --- /dev/null +++ b/framework/src/android/net/apf/ApfCapabilities.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.apf; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.res.Resources; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.R; + +/** + * APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible + * way to drop unwanted network packets to save power. + * + * See documentation at hardware/google/apf/apf.h + * + * This class is immutable. + * @hide + */ +@SystemApi +public final class ApfCapabilities implements Parcelable { + /** + * Version of APF instruction set supported for packet filtering. 0 indicates no support for + * packet filtering using APF programs. + */ + public final int apfVersionSupported; + + /** + * Maximum size of APF program allowed. + */ + public final int maximumApfProgramSize; + + /** + * Format of packets passed to APF filter. Should be one of ARPHRD_* + */ + public final int apfPacketFormat; + + public ApfCapabilities( + int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) { + this.apfVersionSupported = apfVersionSupported; + this.maximumApfProgramSize = maximumApfProgramSize; + this.apfPacketFormat = apfPacketFormat; + } + + private ApfCapabilities(Parcel in) { + apfVersionSupported = in.readInt(); + maximumApfProgramSize = in.readInt(); + apfPacketFormat = in.readInt(); + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(apfVersionSupported); + dest.writeInt(maximumApfProgramSize); + dest.writeInt(apfPacketFormat); + } + + public static final Creator CREATOR = new Creator() { + @Override + public ApfCapabilities createFromParcel(Parcel in) { + return new ApfCapabilities(in); + } + + @Override + public ApfCapabilities[] newArray(int size) { + return new ApfCapabilities[size]; + } + }; + + @NonNull + @Override + public String toString() { + return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(), + apfVersionSupported, maximumApfProgramSize, apfPacketFormat); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ApfCapabilities)) return false; + final ApfCapabilities other = (ApfCapabilities) obj; + return apfVersionSupported == other.apfVersionSupported + && maximumApfProgramSize == other.maximumApfProgramSize + && apfPacketFormat == other.apfPacketFormat; + } + + /** + * Determines whether the APF interpreter advertises support for the data buffer access opcodes + * LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and + * STDW (STore Data Word) support is present from APFv4 on. + * + * @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported. + */ + public boolean hasDataAccess() { + return apfVersionSupported >= 4; + } + + /** + * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. + */ + public static boolean getApfDrop8023Frames() { + return Resources.getSystem().getBoolean(R.bool.config_apfDrop802_3Frames); + } + + /** + * @return An array of denylisted EtherType, packets with EtherTypes within it will be dropped. + */ + public static @NonNull int[] getApfEtherTypeBlackList() { + return Resources.getSystem().getIntArray(R.array.config_apfEthTypeBlackList); + } +} diff --git a/framework/src/android/net/util/DnsUtils.java b/framework/src/android/net/util/DnsUtils.java new file mode 100644 index 0000000000..7908353eed --- /dev/null +++ b/framework/src/android/net/util/DnsUtils.java @@ -0,0 +1,379 @@ +/* + * 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 static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.InetAddresses; +import android.net.Network; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import com.android.internal.util.BitUtils; + +import libcore.io.IoUtils; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * @hide + */ +public class DnsUtils { + private static final String TAG = "DnsUtils"; + private static final int CHAR_BIT = 8; + public static final int IPV6_ADDR_SCOPE_NODELOCAL = 0x01; + public static final int IPV6_ADDR_SCOPE_LINKLOCAL = 0x02; + public static final int IPV6_ADDR_SCOPE_SITELOCAL = 0x05; + public static final int IPV6_ADDR_SCOPE_GLOBAL = 0x0e; + private static final Comparator sRfc6724Comparator = new Rfc6724Comparator(); + + /** + * Comparator to sort SortableAddress in Rfc6724 style. + */ + public static class Rfc6724Comparator implements Comparator { + // This function matches the behaviour of _rfc6724_compare in the native resolver. + @Override + public int compare(SortableAddress span1, SortableAddress span2) { + // Rule 1: Avoid unusable destinations. + if (span1.hasSrcAddr != span2.hasSrcAddr) { + return span2.hasSrcAddr - span1.hasSrcAddr; + } + + // Rule 2: Prefer matching scope. + if (span1.scopeMatch != span2.scopeMatch) { + return span2.scopeMatch - span1.scopeMatch; + } + + // TODO: Implement rule 3: Avoid deprecated addresses. + // TODO: Implement rule 4: Prefer home addresses. + + // Rule 5: Prefer matching label. + if (span1.labelMatch != span2.labelMatch) { + return span2.labelMatch - span1.labelMatch; + } + + // Rule 6: Prefer higher precedence. + if (span1.precedence != span2.precedence) { + return span2.precedence - span1.precedence; + } + + // TODO: Implement rule 7: Prefer native transport. + + // Rule 8: Prefer smaller scope. + if (span1.scope != span2.scope) { + return span1.scope - span2.scope; + } + + // Rule 9: Use longest matching prefix. IPv6 only. + if (span1.prefixMatchLen != span2.prefixMatchLen) { + return span2.prefixMatchLen - span1.prefixMatchLen; + } + + // Rule 10: Leave the order unchanged. Collections.sort is a stable sort. + return 0; + } + } + + /** + * Class used to sort with RFC 6724 + */ + public static class SortableAddress { + public final int label; + public final int labelMatch; + public final int scope; + public final int scopeMatch; + public final int precedence; + public final int prefixMatchLen; + public final int hasSrcAddr; + public final InetAddress address; + + public SortableAddress(@NonNull InetAddress addr, @Nullable InetAddress srcAddr) { + address = addr; + hasSrcAddr = (srcAddr != null) ? 1 : 0; + label = findLabel(addr); + scope = findScope(addr); + precedence = findPrecedence(addr); + labelMatch = ((srcAddr != null) && (label == findLabel(srcAddr))) ? 1 : 0; + scopeMatch = ((srcAddr != null) && (scope == findScope(srcAddr))) ? 1 : 0; + if (isIpv6Address(addr) && isIpv6Address(srcAddr)) { + prefixMatchLen = compareIpv6PrefixMatchLen(srcAddr, addr); + } else { + prefixMatchLen = 0; + } + } + } + + /** + * Sort the given address list in RFC6724 order. + * Will leave the list unchanged if an error occurs. + * + * This function matches the behaviour of _rfc6724_sort in the native resolver. + */ + public static @NonNull List rfc6724Sort(@Nullable Network network, + @NonNull List answers) { + final ArrayList sortableAnswerList = new ArrayList<>(); + for (InetAddress addr : answers) { + sortableAnswerList.add(new SortableAddress(addr, findSrcAddress(network, addr))); + } + + Collections.sort(sortableAnswerList, sRfc6724Comparator); + + final List sortedAnswers = new ArrayList<>(); + for (SortableAddress ans : sortableAnswerList) { + sortedAnswers.add(ans.address); + } + + return sortedAnswers; + } + + private static @Nullable InetAddress findSrcAddress(@Nullable Network network, + @NonNull InetAddress addr) { + final int domain; + if (isIpv4Address(addr)) { + domain = AF_INET; + } else if (isIpv6Address(addr)) { + domain = AF_INET6; + } else { + return null; + } + final FileDescriptor socket; + try { + socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP); + } catch (ErrnoException e) { + Log.e(TAG, "findSrcAddress:" + e.toString()); + return null; + } + try { + if (network != null) network.bindSocket(socket); + Os.connect(socket, new InetSocketAddress(addr, 0)); + return ((InetSocketAddress) Os.getsockname(socket)).getAddress(); + } catch (IOException | ErrnoException e) { + return null; + } finally { + IoUtils.closeQuietly(socket); + } + } + + /** + * Get the label for a given IPv4/IPv6 address. + * RFC 6724, section 2.1. + * + * Note that Java will return an IPv4-mapped address as an IPv4 address. + */ + private static int findLabel(@NonNull InetAddress addr) { + if (isIpv4Address(addr)) { + return 4; + } else if (isIpv6Address(addr)) { + if (addr.isLoopbackAddress()) { + return 0; + } else if (isIpv6Address6To4(addr)) { + return 2; + } else if (isIpv6AddressTeredo(addr)) { + return 5; + } else if (isIpv6AddressULA(addr)) { + return 13; + } else if (((Inet6Address) addr).isIPv4CompatibleAddress()) { + return 3; + } else if (addr.isSiteLocalAddress()) { + return 11; + } else if (isIpv6Address6Bone(addr)) { + return 12; + } else { + // All other IPv6 addresses, including global unicast addresses. + return 1; + } + } else { + // This should never happen. + return 1; + } + } + + private static boolean isIpv6Address(@Nullable InetAddress addr) { + return addr instanceof Inet6Address; + } + + private static boolean isIpv4Address(@Nullable InetAddress addr) { + return addr instanceof Inet4Address; + } + + private static boolean isIpv6Address6To4(@NonNull InetAddress addr) { + if (!isIpv6Address(addr)) return false; + final byte[] byteAddr = addr.getAddress(); + return byteAddr[0] == 0x20 && byteAddr[1] == 0x02; + } + + private static boolean isIpv6AddressTeredo(@NonNull InetAddress addr) { + if (!isIpv6Address(addr)) return false; + final byte[] byteAddr = addr.getAddress(); + return byteAddr[0] == 0x20 && byteAddr[1] == 0x01 && byteAddr[2] == 0x00 + && byteAddr[3] == 0x00; + } + + private static boolean isIpv6AddressULA(@NonNull InetAddress addr) { + return isIpv6Address(addr) && (addr.getAddress()[0] & 0xfe) == 0xfc; + } + + private static boolean isIpv6Address6Bone(@NonNull InetAddress addr) { + if (!isIpv6Address(addr)) return false; + final byte[] byteAddr = addr.getAddress(); + return byteAddr[0] == 0x3f && byteAddr[1] == (byte) 0xfe; + } + + private static int getIpv6MulticastScope(@NonNull InetAddress addr) { + return !isIpv6Address(addr) ? 0 : (addr.getAddress()[1] & 0x0f); + } + + private static int findScope(@NonNull InetAddress addr) { + if (isIpv6Address(addr)) { + if (addr.isMulticastAddress()) { + return getIpv6MulticastScope(addr); + } else if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) { + /** + * RFC 4291 section 2.5.3 says loopback is to be treated as having + * link-local scope. + */ + return IPV6_ADDR_SCOPE_LINKLOCAL; + } else if (addr.isSiteLocalAddress()) { + return IPV6_ADDR_SCOPE_SITELOCAL; + } else { + return IPV6_ADDR_SCOPE_GLOBAL; + } + } else if (isIpv4Address(addr)) { + if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) { + return IPV6_ADDR_SCOPE_LINKLOCAL; + } else { + /** + * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses + * and shared addresses (100.64.0.0/10), are assigned global scope. + */ + return IPV6_ADDR_SCOPE_GLOBAL; + } + } else { + /** + * This should never happen. + * Return a scope with low priority as a last resort. + */ + return IPV6_ADDR_SCOPE_NODELOCAL; + } + } + + /** + * Get the precedence for a given IPv4/IPv6 address. + * RFC 6724, section 2.1. + * + * Note that Java will return an IPv4-mapped address as an IPv4 address. + */ + private static int findPrecedence(@NonNull InetAddress addr) { + if (isIpv4Address(addr)) { + return 35; + } else if (isIpv6Address(addr)) { + if (addr.isLoopbackAddress()) { + return 50; + } else if (isIpv6Address6To4(addr)) { + return 30; + } else if (isIpv6AddressTeredo(addr)) { + return 5; + } else if (isIpv6AddressULA(addr)) { + return 3; + } else if (((Inet6Address) addr).isIPv4CompatibleAddress() || addr.isSiteLocalAddress() + || isIpv6Address6Bone(addr)) { + return 1; + } else { + // All other IPv6 addresses, including global unicast addresses. + return 40; + } + } else { + return 1; + } + } + + /** + * Find number of matching initial bits between the two addresses. + */ + private static int compareIpv6PrefixMatchLen(@NonNull InetAddress srcAddr, + @NonNull InetAddress dstAddr) { + final byte[] srcByte = srcAddr.getAddress(); + final byte[] dstByte = dstAddr.getAddress(); + + // This should never happen. + if (srcByte.length != dstByte.length) return 0; + + for (int i = 0; i < dstByte.length; ++i) { + if (srcByte[i] == dstByte[i]) { + continue; + } + int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]); + return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24); // Java ints are 32 bits + } + return dstByte.length * CHAR_BIT; + } + + /** + * Check if given network has Ipv4 capability + * This function matches the behaviour of have_ipv4 in the native resolver. + */ + public static boolean haveIpv4(@Nullable Network network) { + final SocketAddress addrIpv4 = + new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0); + return checkConnectivity(network, AF_INET, addrIpv4); + } + + /** + * Check if given network has Ipv6 capability + * This function matches the behaviour of have_ipv6 in the native resolver. + */ + public static boolean haveIpv6(@Nullable Network network) { + final SocketAddress addrIpv6 = + new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0); + return checkConnectivity(network, AF_INET6, addrIpv6); + } + + private static boolean checkConnectivity(@Nullable Network network, + int domain, @NonNull SocketAddress addr) { + final FileDescriptor socket; + try { + socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP); + } catch (ErrnoException e) { + return false; + } + try { + if (network != null) network.bindSocket(socket); + Os.connect(socket, addr); + } catch (IOException | ErrnoException e) { + return false; + } finally { + IoUtils.closeQuietly(socket); + } + return true; + } +} diff --git a/framework/src/android/net/util/KeepaliveUtils.java b/framework/src/android/net/util/KeepaliveUtils.java new file mode 100644 index 0000000000..bfc4563fbf --- /dev/null +++ b/framework/src/android/net/util/KeepaliveUtils.java @@ -0,0 +1,115 @@ +/* + * 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.annotation.NonNull; +import android.content.Context; +import android.content.res.Resources; +import android.net.NetworkCapabilities; +import android.text.TextUtils; +import android.util.AndroidRuntimeException; + +import com.android.internal.R; + +/** + * Collection of utilities for socket keepalive offload. + * + * @hide + */ +public final class KeepaliveUtils { + + public static final String TAG = "KeepaliveUtils"; + + public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException { + public KeepaliveDeviceConfigurationException(final String msg) { + super(msg); + } + } + + /** + * Read supported keepalive count for each transport type from overlay resource. This should be + * used to create a local variable store of resource customization, and use it as the input for + * {@link getSupportedKeepalivesForNetworkCapabilities}. + * + * @param context The context to read resource from. + * @return An array of supported keepalive count for each transport type. + */ + @NonNull + public static int[] getSupportedKeepalives(@NonNull Context context) { + String[] res = null; + try { + res = context.getResources().getStringArray( + R.array.config_networkSupportedKeepaliveCount); + } catch (Resources.NotFoundException unused) { + } + if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource"); + + final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1]; + for (final String row : res) { + if (TextUtils.isEmpty(row)) { + throw new KeepaliveDeviceConfigurationException("Empty string"); + } + final String[] arr = row.split(","); + if (arr.length != 2) { + throw new KeepaliveDeviceConfigurationException("Invalid parameter length"); + } + + int transport; + int supported; + try { + transport = Integer.parseInt(arr[0]); + supported = Integer.parseInt(arr[1]); + } catch (NumberFormatException e) { + throw new KeepaliveDeviceConfigurationException("Invalid number format"); + } + + if (!NetworkCapabilities.isValidTransport(transport)) { + throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport); + } + + if (supported < 0) { + throw new KeepaliveDeviceConfigurationException( + "Invalid supported count " + supported + " for " + + NetworkCapabilities.transportNameOf(transport)); + } + ret[transport] = supported; + } + return ret; + } + + /** + * Get supported keepalive count for the given {@link NetworkCapabilities}. + * + * @param supportedKeepalives An array of supported keepalive count for each transport type. + * @param nc The {@link NetworkCapabilities} of the network the socket keepalive is on. + * + * @return Supported keepalive count for the given {@link NetworkCapabilities}. + */ + public static int getSupportedKeepalivesForNetworkCapabilities( + @NonNull int[] supportedKeepalives, @NonNull NetworkCapabilities nc) { + final int[] transports = nc.getTransportTypes(); + if (transports.length == 0) return 0; + int supportedCount = supportedKeepalives[transports[0]]; + // Iterate through transports and return minimum supported value. + for (final int transport : transports) { + if (supportedCount > supportedKeepalives[transport]) { + supportedCount = supportedKeepalives[transport]; + } + } + return supportedCount; + } +} diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java new file mode 100644 index 0000000000..85e3fa3048 --- /dev/null +++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; +import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; + +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.List; + +/** + * A class to encapsulate management of the "Smart Networking" capability of + * avoiding bad Wi-Fi when, for example upstream connectivity is lost or + * certain critical link failures occur. + * + * This enables the device to switch to another form of connectivity, like + * mobile, if it's available and working. + * + * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied + * Handler' whenever the computed "avoid bad wifi" value changes. + * + * Disabling this reverts the device to a level of networking sophistication + * circa 2012-13 by disabling disparate code paths each of which contribute to + * maintaining continuous, working Internet connectivity. + * + * @hide + */ +public class MultinetworkPolicyTracker { + private static String TAG = MultinetworkPolicyTracker.class.getSimpleName(); + + private final Context mContext; + private final Handler mHandler; + private final Runnable mAvoidBadWifiCallback; + private final List mSettingsUris; + private final ContentResolver mResolver; + private final SettingObserver mSettingObserver; + private final BroadcastReceiver mBroadcastReceiver; + + private volatile boolean mAvoidBadWifi = true; + private volatile int mMeteredMultipathPreference; + private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + public MultinetworkPolicyTracker(Context ctx, Handler handler) { + this(ctx, handler, null); + } + + public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) { + mContext = ctx; + mHandler = handler; + mAvoidBadWifiCallback = avoidBadWifiCallback; + mSettingsUris = Arrays.asList( + Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), + Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); + mResolver = mContext.getContentResolver(); + mSettingObserver = new SettingObserver(); + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + reevaluateInternal(); + } + }; + + ctx.getSystemService(TelephonyManager.class).listen( + new PhoneStateListener(handler.getLooper()) { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveSubId = subId; + reevaluateInternal(); + } + }, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + + updateAvoidBadWifi(); + updateMeteredMultipathPreference(); + } + + public void start() { + for (Uri uri : mSettingsUris) { + mResolver.registerContentObserver(uri, false, mSettingObserver); + } + + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter, + null /* broadcastPermission */, mHandler); + + reevaluate(); + } + + public void shutdown() { + mResolver.unregisterContentObserver(mSettingObserver); + + mContext.unregisterReceiver(mBroadcastReceiver); + } + + public boolean getAvoidBadWifi() { + return mAvoidBadWifi; + } + + // TODO: move this to MultipathPolicyTracker. + public int getMeteredMultipathPreference() { + return mMeteredMultipathPreference; + } + + /** + * Whether the device or carrier configuration disables avoiding bad wifi by default. + */ + public boolean configRestrictsAvoidBadWifi() { + return (getResourcesForActiveSubId().getInteger(R.integer.config_networkAvoidBadWifi) == 0); + } + + @NonNull + private Resources getResourcesForActiveSubId() { + return SubscriptionManager.getResourcesForSubId(mContext, mActiveSubId); + } + + /** + * Whether we should display a notification when wifi becomes unvalidated. + */ + public boolean shouldNotifyWifiUnvalidated() { + return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null; + } + + public String getAvoidBadWifiSetting() { + return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI); + } + + @VisibleForTesting + public void reevaluate() { + mHandler.post(this::reevaluateInternal); + } + + /** + * Reevaluate the settings. Must be called on the handler thread. + */ + private void reevaluateInternal() { + if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) { + mAvoidBadWifiCallback.run(); + } + updateMeteredMultipathPreference(); + } + + public boolean updateAvoidBadWifi() { + final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting()); + final boolean prev = mAvoidBadWifi; + mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi(); + return mAvoidBadWifi != prev; + } + + /** + * The default (device and carrier-dependent) value for metered multipath preference. + */ + public int configMeteredMultipathPreference() { + return mContext.getResources().getInteger( + R.integer.config_networkMeteredMultipathPreference); + } + + public void updateMeteredMultipathPreference() { + String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE); + try { + mMeteredMultipathPreference = Integer.parseInt(setting); + } catch (NumberFormatException e) { + mMeteredMultipathPreference = configMeteredMultipathPreference(); + } + } + + private class SettingObserver extends ContentObserver { + public SettingObserver() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + Log.wtf(TAG, "Should never be reached."); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (!mSettingsUris.contains(uri)) { + Log.wtf(TAG, "Unexpected settings observation: " + uri); + } + reevaluate(); + } + } +} diff --git a/framework/src/android/net/util/SocketUtils.java b/framework/src/android/net/util/SocketUtils.java new file mode 100644 index 0000000000..e64060f1b2 --- /dev/null +++ b/framework/src/android/net/util/SocketUtils.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2015 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 static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_BINDTODEVICE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.net.NetworkUtils; +import android.system.ErrnoException; +import android.system.NetlinkSocketAddress; +import android.system.Os; +import android.system.PacketSocketAddress; + +import libcore.io.IoBridge; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketAddress; + +/** + * Collection of utilities to interact with raw sockets. + * @hide + */ +@SystemApi +public final class SocketUtils { + /** + * Create a raw datagram socket that is bound to an interface. + * + *

Data sent through the socket will go directly to the underlying network, ignoring VPNs. + */ + public static void bindSocketToInterface(@NonNull FileDescriptor socket, @NonNull String iface) + throws ErrnoException { + // SO_BINDTODEVICE actually takes a string. This works because the first member + // of struct ifreq is a NULL-terminated interface name. + // TODO: add a setsockoptString() + Os.setsockoptIfreq(socket, SOL_SOCKET, SO_BINDTODEVICE, iface); + NetworkUtils.protectFromVpn(socket); + } + + /** + * Make a socket address to communicate with netlink. + */ + @NonNull + public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) { + return new NetlinkSocketAddress(portId, groupsMask); + } + + /** + * Make socket address that packet sockets can bind to. + * + * @param protocol the layer 2 protocol of the packets to receive. One of the {@code ETH_P_*} + * constants in {@link android.system.OsConstants}. + * @param ifIndex the interface index on which packets will be received. + */ + @NonNull + public static SocketAddress makePacketSocketAddress(int protocol, int ifIndex) { + return new PacketSocketAddress( + protocol /* sll_protocol */, + ifIndex /* sll_ifindex */, + null /* sll_addr */); + } + + /** + * Make a socket address that packet socket can send packets to. + * @deprecated Use {@link #makePacketSocketAddress(int, int, byte[])} instead. + * + * @param ifIndex the interface index on which packets will be sent. + * @param hwAddr the hardware address to which packets will be sent. + */ + @Deprecated + @NonNull + public static SocketAddress makePacketSocketAddress(int ifIndex, @NonNull byte[] hwAddr) { + return new PacketSocketAddress( + 0 /* sll_protocol */, + ifIndex /* sll_ifindex */, + hwAddr /* sll_addr */); + } + + /** + * Make a socket address that a packet socket can send packets to. + * + * @param protocol the layer 2 protocol of the packets to send. One of the {@code ETH_P_*} + * constants in {@link android.system.OsConstants}. + * @param ifIndex the interface index on which packets will be sent. + * @param hwAddr the hardware address to which packets will be sent. + */ + @NonNull + public static SocketAddress makePacketSocketAddress(int protocol, int ifIndex, + @NonNull byte[] hwAddr) { + return new PacketSocketAddress( + protocol /* sll_protocol */, + ifIndex /* sll_ifindex */, + hwAddr /* sll_addr */); + } + + /** + * @see IoBridge#closeAndSignalBlockedThreads(FileDescriptor) + */ + public static void closeSocket(@Nullable FileDescriptor fd) throws IOException { + IoBridge.closeAndSignalBlockedThreads(fd); + } + + private SocketUtils() {} +} From 91444cae5662c194f82184e4a73e19eb1cbc66d5 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 15 Jan 2021 23:02:47 +0900 Subject: [PATCH 009/232] Have connectivity self-register manager classes As connectivity services are planned to move to a separate module, move the manager classes registration from SystemServiceRegistry to ConnectivityServicesRegistrar, using the registerContextAwareService APIs. This follows patterns and naming in WifiFrameworkInitializer. Bug: 171540887 Test: device boots, connectivity working Change-Id: I62ced1275750c73f209bac8ec3a3204b95695b83 --- .../net/ConnectivityFrameworkInitializer.java | 83 +++++++++++++++++++ .../src/android/net/ConnectivityManager.java | 22 +++++ 2 files changed, 105 insertions(+) create mode 100644 framework/src/android/net/ConnectivityFrameworkInitializer.java diff --git a/framework/src/android/net/ConnectivityFrameworkInitializer.java b/framework/src/android/net/ConnectivityFrameworkInitializer.java new file mode 100644 index 0000000000..9afa5d1311 --- /dev/null +++ b/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -0,0 +1,83 @@ +/* + * 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 android.net; + +import android.annotation.SystemApi; +import android.app.SystemServiceRegistry; +import android.content.Context; + +/** + * Class for performing registration for all core connectivity services. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class ConnectivityFrameworkInitializer { + private ConnectivityFrameworkInitializer() {} + + /** + * Called by {@link SystemServiceRegistry}'s static initializer and registers all core + * connectivity services to {@link Context}, so that {@link Context#getSystemService} can + * return them. + * + * @throws IllegalStateException if this is called anywhere besides + * {@link SystemServiceRegistry}. + */ + public static void registerServiceWrappers() { + // registerContextAwareService will throw if this is called outside of SystemServiceRegistry + // initialization. + SystemServiceRegistry.registerContextAwareService( + Context.CONNECTIVITY_SERVICE, + ConnectivityManager.class, + (context, serviceBinder) -> { + IConnectivityManager icm = IConnectivityManager.Stub.asInterface(serviceBinder); + return new ConnectivityManager(context, icm); + } + ); + + // TODO: move outside of the connectivity JAR + SystemServiceRegistry.registerContextAwareService( + Context.VPN_MANAGEMENT_SERVICE, + VpnManager.class, + (context) -> { + final ConnectivityManager cm = context.getSystemService( + ConnectivityManager.class); + return cm.createVpnManager(); + } + ); + + SystemServiceRegistry.registerContextAwareService( + Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, + ConnectivityDiagnosticsManager.class, + (context) -> { + final ConnectivityManager cm = context.getSystemService( + ConnectivityManager.class); + return cm.createDiagnosticsManager(); + } + ); + + SystemServiceRegistry.registerContextAwareService( + Context.TEST_NETWORK_SERVICE, + TestNetworkManager.class, + context -> { + final ConnectivityManager cm = context.getSystemService( + ConnectivityManager.class); + return cm.startOrGetTestNetworkManager(); + } + ); + } +} diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 7f07bba668..987dcc4898 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4823,6 +4823,28 @@ public class ConnectivityManager { } } + /** @hide */ + public TestNetworkManager startOrGetTestNetworkManager() { + final IBinder tnBinder; + try { + tnBinder = mService.startOrGetTestNetworkService(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); + } + + /** @hide */ + public VpnManager createVpnManager() { + return new VpnManager(mContext, mService); + } + + /** @hide */ + public ConnectivityDiagnosticsManager createDiagnosticsManager() { + return new ConnectivityDiagnosticsManager(mContext, mService); + } + /** * Simulates a Data Stall for the specified Network. * From 3c2297a2ab1ac0dfce2e351bc8dbd9881a2059e5 Mon Sep 17 00:00:00 2001 From: paulhu Date: Mon, 1 Feb 2021 16:30:08 +0800 Subject: [PATCH 010/232] Remove ArrayUtils usage in connectivity frameworks classes Instead, use CollectionUtils from frameworks/libs/net. Bug: 174541037 Test: atest FrameworksNetTests Change-Id: I610e00302cf76510e9e34ac8a9a5f738e5ecd0c7 --- framework/src/android/net/NetworkCapabilities.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 3843b9ab93..0832152d6a 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -34,9 +34,9 @@ import android.util.ArraySet; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.BitUtils; import com.android.internal.util.Preconditions; +import com.android.net.module.util.CollectionUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -767,7 +767,7 @@ public final class NetworkCapabilities implements Parcelable { if (originalOwnerUid == creatorUid) { setOwnerUid(creatorUid); } - if (ArrayUtils.contains(originalAdministratorUids, creatorUid)) { + if (CollectionUtils.contains(originalAdministratorUids, creatorUid)) { setAdministratorUids(new int[] {creatorUid}); } // There is no need to clear the UIDs, they have already been cleared by clearAll() above. @@ -1873,7 +1873,7 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" OwnerUid: ").append(mOwnerUid); } - if (!ArrayUtils.isEmpty(mAdministratorUids)) { + if (mAdministratorUids != null && mAdministratorUids.length != 0) { sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids)); } @@ -2506,7 +2506,7 @@ public final class NetworkCapabilities implements Parcelable { @NonNull public NetworkCapabilities build() { if (mCaps.getOwnerUid() != Process.INVALID_UID) { - if (!ArrayUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) { + if (!CollectionUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) { throw new IllegalStateException("The owner UID must be included in " + " administrator UIDs."); } From 515b26ae980b83ad9864bedd468a90937f457e33 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Fri, 29 Jan 2021 12:15:00 +0100 Subject: [PATCH 011/232] API: Suppress existing NullableCollections lints Bug: 152525509 Bug: 154763999 Test: make checkapi Change-Id: Iecd0927e6be5496b2fbd1a49396db5439257ffe4 --- framework/src/android/net/NetworkAgent.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index d22d82d1f4..27aa15d1e1 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -775,7 +776,8 @@ public abstract class NetworkAgent { * @param underlyingNetworks the new list of underlying networks. * @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])} */ - public final void setUnderlyingNetworks(@Nullable List underlyingNetworks) { + public final void setUnderlyingNetworks( + @SuppressLint("NullableCollection") @Nullable List underlyingNetworks) { final ArrayList underlyingArray = (underlyingNetworks != null) ? new ArrayList<>(underlyingNetworks) : null; queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray)); From d5c54eca669bb186e7a737c0a1f6188efee23229 Mon Sep 17 00:00:00 2001 From: junyulai Date: Fri, 29 Jan 2021 00:25:31 +0800 Subject: [PATCH 012/232] [VCN08] Expose NOT_VCN_MANAGED capability as system API Test: m -j doc-comment-check-docs Fix: 177299683 Bug: 175662146 Change-Id: Iaa53d21774ea48af5fe339b57bf1235c77f295a0 --- framework/src/android/net/NetworkCapabilities.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 3843b9ab93..e9b9a9362f 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -401,11 +401,18 @@ public final class NetworkCapabilities implements Parcelable { public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; /** - * Indicates that this network is not managed by a Virtual Carrier Network (VCN). - * - * TODO(b/177299683): Add additional clarifying javadoc. + * Indicates that this network is not subsumed by a Virtual Carrier Network (VCN). + *

+ * To provide an experience on a VCN similar to a single traditional carrier network, in + * some cases the system sets this bit is set by default in application's network requests, + * and may choose to remove it at its own discretion when matching the request to a network. + *

+ * Applications that want to know about a Virtual Carrier Network's underlying networks, + * for example to use them for multipath purposes, should remove this bit from their network + * requests ; the system will not add it back once removed. * @hide */ + @SystemApi public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; From f4a7b149c1bd3d0951b58652038f6e5c0e91d40d Mon Sep 17 00:00:00 2001 From: lucaslin Date: Tue, 2 Feb 2021 12:57:26 +0800 Subject: [PATCH 013/232] Reimplement [read|write]ArraySet of Parcel inside NetworkCapabilities Both of readArraySet and writeArraySet are hidden APIs inside Parcel, so reimplement those two methods inside NetworkCapabilities to prevent calling hidden APIs. Bug: 170598012 Test: atest FrameworksNetTests Change-Id: I70f75c1a0db92c6a5575c6a9917843d783dfaeea --- .../src/android/net/NetworkCapabilities.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 3843b9ab93..a9fd38198b 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -1779,6 +1779,15 @@ public final class NetworkCapabilities implements Parcelable { return 0; } + private void writeParcelableArraySet(Parcel in, + @Nullable ArraySet val, int flags) { + final int size = (val != null) ? val.size() : -1; + in.writeInt(size); + for (int i = 0; i < size; i++) { + in.writeParcelable(val.valueAt(i), flags); + } + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mNetworkCapabilities); @@ -1789,7 +1798,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeParcelable((Parcelable) mNetworkSpecifier, flags); dest.writeParcelable((Parcelable) mTransportInfo, flags); dest.writeInt(mSignalStrength); - dest.writeArraySet(mUids); + writeParcelableArraySet(dest, mUids, flags); dest.writeString(mSSID); dest.writeBoolean(mPrivateDnsBroken); dest.writeIntArray(getAdministratorUids()); @@ -1812,8 +1821,7 @@ public final class NetworkCapabilities implements Parcelable { netCap.mNetworkSpecifier = in.readParcelable(null); netCap.mTransportInfo = in.readParcelable(null); netCap.mSignalStrength = in.readInt(); - netCap.mUids = (ArraySet) in.readArraySet( - null /* ClassLoader, null for default */); + netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */); netCap.mSSID = in.readString(); netCap.mPrivateDnsBroken = in.readBoolean(); netCap.setAdministratorUids(in.createIntArray()); @@ -1826,6 +1834,20 @@ public final class NetworkCapabilities implements Parcelable { public NetworkCapabilities[] newArray(int size) { return new NetworkCapabilities[size]; } + + private @Nullable ArraySet readParcelableArraySet(Parcel in, + @Nullable ClassLoader loader) { + final int size = in.readInt(); + if (size < 0) { + return null; + } + final ArraySet result = new ArraySet<>(size); + for (int i = 0; i < size; i++) { + final T value = in.readParcelable(loader); + result.append(value); + } + return result; + } }; @Override From a858d3bfebb4c61bb1dac771f0a0053c54e0f349 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Wed, 3 Feb 2021 18:40:42 +0800 Subject: [PATCH 014/232] Move shared methods to net shared lib The NetworkUtils.getNetworkPart() method is used by Settings and wifi. NetworkUtils is inside the incoming connectivity module. It will become inaccessible outside the module, so move the shared methods to shared lib and also move the related helper method. The corresponding usgae is also updated. Bug: 172183305 Test: atest FrameworksNetTests Change-Id: I47410afa27aad61f63759df41be959f323c1d100 --- framework/src/android/net/IpPrefix.java | 8 ++-- framework/src/android/net/NetworkUtils.java | 41 --------------------- 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/framework/src/android/net/IpPrefix.java b/framework/src/android/net/IpPrefix.java index e7c801475c..e970065e8f 100644 --- a/framework/src/android/net/IpPrefix.java +++ b/framework/src/android/net/IpPrefix.java @@ -23,6 +23,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; +import com.android.net.module.util.NetUtils; + import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -58,7 +60,7 @@ public final class IpPrefix implements Parcelable { throw new IllegalArgumentException( "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); } - NetworkUtils.maskRawAddress(address, prefixLength); + NetUtils.maskRawAddress(address, prefixLength); } /** @@ -189,7 +191,7 @@ public final class IpPrefix implements Parcelable { if (addrBytes == null || addrBytes.length != this.address.length) { return false; } - NetworkUtils.maskRawAddress(addrBytes, prefixLength); + NetUtils.maskRawAddress(addrBytes, prefixLength); return Arrays.equals(this.address, addrBytes); } @@ -203,7 +205,7 @@ public final class IpPrefix implements Parcelable { public boolean containsPrefix(@NonNull IpPrefix otherPrefix) { if (otherPrefix.getPrefixLength() < prefixLength) return false; final byte[] otherAddress = otherPrefix.getRawAddress(); - NetworkUtils.maskRawAddress(otherAddress, prefixLength); + NetUtils.maskRawAddress(otherAddress, prefixLength); return Arrays.equals(otherAddress, address); } diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java index 8be4af7b13..9ccb04a44a 100644 --- a/framework/src/android/net/NetworkUtils.java +++ b/framework/src/android/net/NetworkUtils.java @@ -29,7 +29,6 @@ import java.math.BigInteger; import java.net.Inet4Address; import java.net.InetAddress; import java.net.SocketException; -import java.net.UnknownHostException; import java.util.Locale; import java.util.TreeSet; @@ -231,46 +230,6 @@ public class NetworkUtils { return InetAddress.parseNumericAddress(addrString); } - /** - * Masks a raw IP address byte array with the specified prefix length. - */ - public static void maskRawAddress(byte[] array, int prefixLength) { - if (prefixLength < 0 || prefixLength > array.length * 8) { - throw new RuntimeException("IP address with " + array.length + - " bytes has invalid prefix length " + prefixLength); - } - - int offset = prefixLength / 8; - int remainder = prefixLength % 8; - byte mask = (byte)(0xFF << (8 - remainder)); - - if (offset < array.length) array[offset] = (byte)(array[offset] & mask); - - offset++; - - for (; offset < array.length; offset++) { - array[offset] = 0; - } - } - - /** - * Get InetAddress masked with prefixLength. Will never return null. - * @param address the IP address to mask with - * @param prefixLength the prefixLength used to mask the IP - */ - public static InetAddress getNetworkPart(InetAddress address, int prefixLength) { - byte[] array = address.getAddress(); - maskRawAddress(array, prefixLength); - - InetAddress netPart = null; - try { - netPart = InetAddress.getByAddress(array); - } catch (UnknownHostException e) { - throw new RuntimeException("getNetworkPart error - " + e.toString()); - } - return netPart; - } - /** * Returns the implicit netmask of an IPv4 address, as was the custom before 1993. */ From 470efc161d61fbd9843288f1895e5f17226baa26 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 4 Feb 2021 18:04:43 +0900 Subject: [PATCH 015/232] Remove legacy network factories Nothing on the system is using registerNetworkFactory, unregisterNetworkFactory at the moment. registerNetworkFactory, unregisterNetworkFactory are protected by signature permissions, so could not be used by anything outside of the system. Remove the two methods and the underlying support for this legacy, deprecated mechanism. Bug: 179229316 Test: atest FrameworksNetTests Change-Id: I7cdc9eed67f846c8774474af038133040aeccab3 --- .../src/android/net/ConnectivityManager.java | 26 ------------------- .../src/android/net/IConnectivityManager.aidl | 3 --- 2 files changed, 29 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 987dcc4898..06dec9335d 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3231,32 +3231,6 @@ public class ConnectivityManager { } } - /** {@hide} - returns the factory serial number */ - @UnsupportedAppUsage - @RequiresPermission(anyOf = { - NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_FACTORY}) - public int registerNetworkFactory(Messenger messenger, String name) { - try { - return mService.registerNetworkFactory(messenger, name); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** {@hide} */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - @RequiresPermission(anyOf = { - NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_FACTORY}) - public void unregisterNetworkFactory(Messenger messenger) { - try { - mService.unregisterNetworkFactory(messenger); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Registers the specified {@link NetworkProvider}. * Each listener must only be registered once. The listener can be unregistered with diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 1b4d2e4139..db8b7ed3b1 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -156,9 +156,6 @@ interface IConnectivityManager boolean requestBandwidthUpdate(in Network network); - int registerNetworkFactory(in Messenger messenger, in String name); - void unregisterNetworkFactory(in Messenger messenger); - int registerNetworkProvider(in Messenger messenger, in String name); void unregisterNetworkProvider(in Messenger messenger); From 062b6480995ba69faa8b4c8bab08e2b01137fa4a Mon Sep 17 00:00:00 2001 From: junyulai Date: Wed, 13 Jan 2021 18:13:11 +0800 Subject: [PATCH 016/232] [VCN07] Bypass VCN for non-internet app accessible cellular services Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities and user intention, which includes: 1. For the requests that don't have anything besides VCN_SUPPORTED_CAPABILITIES, add the NOT_VCN_MANAGED to allow the callers automatically utilize VCN networks if available. 2. For the requests that explicitly add or remove NOT_VCN_MANAGED, do not alter them to allow user fire request that suits their need. Test: atest NetworkRequestTest#testBypassingVcnForNonInternetRequest Bug: 175662146 Change-Id: I9936894b9530a22fb186cfd25cbee0fced65b72b --- framework/src/android/net/NetworkRequest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 04011fc681..9883692e7d 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -16,6 +16,22 @@ package android.net; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -30,6 +46,8 @@ import android.os.Process; import android.text.TextUtils; import android.util.proto.ProtoOutputStream; +import java.util.Arrays; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -156,8 +174,30 @@ public class NetworkRequest implements Parcelable { * needed in terms of {@link NetworkCapabilities} features */ public static class Builder { + /** + * Capabilities that are currently compatible with VCN networks. + */ + private static final List VCN_SUPPORTED_CAPABILITIES = Arrays.asList( + NET_CAPABILITY_CAPTIVE_PORTAL, + NET_CAPABILITY_DUN, + NET_CAPABILITY_FOREGROUND, + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_NOT_CONGESTED, + NET_CAPABILITY_NOT_METERED, + NET_CAPABILITY_NOT_RESTRICTED, + NET_CAPABILITY_NOT_ROAMING, + NET_CAPABILITY_NOT_SUSPENDED, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NET_CAPABILITY_TEMPORARILY_NOT_METERED, + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_VALIDATED); + private final NetworkCapabilities mNetworkCapabilities; + // A boolean that represents the user modified NOT_VCN_MANAGED capability. + private boolean mModifiedNotVcnManaged = false; + /** * Default constructor for Builder. */ @@ -179,6 +219,7 @@ public class NetworkRequest implements Parcelable { // maybeMarkCapabilitiesRestricted() doesn't add back. final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities); nc.maybeMarkCapabilitiesRestricted(); + deduceNotVcnManagedCapability(nc); return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE, ConnectivityManager.REQUEST_ID_UNSET, Type.NONE); } @@ -195,6 +236,9 @@ public class NetworkRequest implements Parcelable { */ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addCapability(capability); + if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { + mModifiedNotVcnManaged = true; + } return this; } @@ -206,6 +250,9 @@ public class NetworkRequest implements Parcelable { */ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeCapability(capability); + if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { + mModifiedNotVcnManaged = true; + } return this; } @@ -263,6 +310,9 @@ public class NetworkRequest implements Parcelable { @NonNull public Builder clearCapabilities() { mNetworkCapabilities.clearAll(); + // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities + // should not be add back later. + mModifiedNotVcnManaged = true; return this; } @@ -382,6 +432,25 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setSignalStrength(signalStrength); return this; } + + /** + * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities + * and user intention, which includes: + * 1. For the requests that don't have anything besides + * {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to + * allow the callers automatically utilize VCN networks if available. + * 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED, + * do not alter them to allow user fire request that suits their need. + * + * @hide + */ + private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) { + if (mModifiedNotVcnManaged) return; + for (final int cap : nc.getCapabilities()) { + if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return; + } + nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + } } // implement the Parcelable interface From 452c6ffa61c5d0a11c79e9311ce12b4da486e97b Mon Sep 17 00:00:00 2001 From: James Mattis Date: Fri, 1 Jan 2021 14:13:35 -0800 Subject: [PATCH 017/232] Stubbed setOemNetworkPreference in Connectivity Stubbed setOemNetworkPreference() in ConnectivityService and connected it to ConnectivityManager. Bug: 176495254 Bug: 176494815 Test: atest FrameworksNetTests atest FrameworksNetIntegrationTests atest CtsNetTestCasesLatestSdk Change-Id: Iabad7300a8b058e1edcb0defab8a031d21e6433c --- framework/src/android/net/ConnectivityManager.java | 10 +++++++--- framework/src/android/net/IConnectivityManager.aidl | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 987dcc4898..c162b4396e 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4871,9 +4871,13 @@ public class ConnectivityManager { } } - private void setOemNetworkPreference(@NonNull OemNetworkPreferences preference) { - Log.d(TAG, "setOemNetworkPreference called with preference: " - + preference.toString()); + private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { + try { + mService.setOemNetworkPreference(preference); + } catch (RemoteException e) { + Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); + throw e.rethrowFromSystemServer(); + } } @NonNull diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 1b4d2e4139..615f54dde9 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -29,6 +29,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.NetworkState; +import android.net.OemNetworkPreferences; import android.net.ProxyInfo; import android.net.UidRange; import android.net.QosSocketInfo; @@ -243,4 +244,6 @@ interface IConnectivityManager void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); void unregisterQosCallback(in IQosCallback callback); + + void setOemNetworkPreference(in OemNetworkPreferences preference); } From 0b6f2908c5411aa8d39ad7aab1b10a398c7535dc Mon Sep 17 00:00:00 2001 From: Roman Kalukiewicz Date: Wed, 14 Oct 2020 15:59:06 -0700 Subject: [PATCH 018/232] Add @Nullable to Object#equals() This is a partial cherry-pick of change: I5eedb571c9d78862115dfdc5dae1cf2a35343580 for connectivity classes. Bug: 170883422 Test: m Merged-In: I5eedb571c9d78862115dfdc5dae1cf2a35343580 Change-Id: I7dc661863b73f4198ddb4f3a1566583d0f07db3c --- framework/src/android/net/CaptivePortalData.java | 2 +- framework/src/android/net/IpConfiguration.java | 2 +- framework/src/android/net/IpPrefix.java | 3 ++- framework/src/android/net/LinkAddress.java | 2 +- framework/src/android/net/LinkProperties.java | 2 +- framework/src/android/net/MacAddress.java | 2 +- framework/src/android/net/Network.java | 3 ++- framework/src/android/net/NetworkRequest.java | 2 +- framework/src/android/net/ProxyInfo.java | 2 +- framework/src/android/net/RouteInfo.java | 4 ++-- 10 files changed, 13 insertions(+), 11 deletions(-) diff --git a/framework/src/android/net/CaptivePortalData.java b/framework/src/android/net/CaptivePortalData.java index 18467fad8e..9b56b23cc8 100644 --- a/framework/src/android/net/CaptivePortalData.java +++ b/framework/src/android/net/CaptivePortalData.java @@ -276,7 +276,7 @@ public final class CaptivePortalData implements Parcelable { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (!(obj instanceof CaptivePortalData)) return false; final CaptivePortalData other = (CaptivePortalData) obj; return mRefreshTimeMillis == other.mRefreshTimeMillis diff --git a/framework/src/android/net/IpConfiguration.java b/framework/src/android/net/IpConfiguration.java index 0b205642b3..d5f8b2edb6 100644 --- a/framework/src/android/net/IpConfiguration.java +++ b/framework/src/android/net/IpConfiguration.java @@ -167,7 +167,7 @@ public final class IpConfiguration implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o == this) { return true; } diff --git a/framework/src/android/net/IpPrefix.java b/framework/src/android/net/IpPrefix.java index e7c801475c..bcb65fab85 100644 --- a/framework/src/android/net/IpPrefix.java +++ b/framework/src/android/net/IpPrefix.java @@ -18,6 +18,7 @@ package android.net; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -124,7 +125,7 @@ public final class IpPrefix implements Parcelable { * @return {@code true} if both objects are equal, {@code false} otherwise. */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (!(obj instanceof IpPrefix)) { return false; } diff --git a/framework/src/android/net/LinkAddress.java b/framework/src/android/net/LinkAddress.java index 44d25a1ab0..d1bdaa078c 100644 --- a/framework/src/android/net/LinkAddress.java +++ b/framework/src/android/net/LinkAddress.java @@ -349,7 +349,7 @@ public class LinkAddress implements Parcelable { * @return {@code true} if both objects are equal, {@code false} otherwise. */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (!(obj instanceof LinkAddress)) { return false; } diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java index 486e2d74dd..e41ed72b25 100644 --- a/framework/src/android/net/LinkProperties.java +++ b/framework/src/android/net/LinkProperties.java @@ -1613,7 +1613,7 @@ public final class LinkProperties implements Parcelable { * @return {@code true} if both objects are equal, {@code false} otherwise. */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (!(obj instanceof LinkProperties)) return false; diff --git a/framework/src/android/net/MacAddress.java b/framework/src/android/net/MacAddress.java index c7116b41e8..c83c23a4b6 100644 --- a/framework/src/android/net/MacAddress.java +++ b/framework/src/android/net/MacAddress.java @@ -161,7 +161,7 @@ public final class MacAddress implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr; } diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java index b07bd68a0f..46141e0d0c 100644 --- a/framework/src/android/net/Network.java +++ b/framework/src/android/net/Network.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -510,7 +511,7 @@ public class Network implements Parcelable { }; @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (!(obj instanceof Network)) return false; Network other = (Network)obj; return this.netId == other.netId; diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 04011fc681..c4d1b09a5c 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -567,7 +567,7 @@ public class NetworkRequest implements Parcelable { proto.end(token); } - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (obj instanceof NetworkRequest == false) return false; NetworkRequest that = (NetworkRequest)obj; return (that.legacyType == this.legacyType && diff --git a/framework/src/android/net/ProxyInfo.java b/framework/src/android/net/ProxyInfo.java index c9bca2876b..9c9fed1028 100644 --- a/framework/src/android/net/ProxyInfo.java +++ b/framework/src/android/net/ProxyInfo.java @@ -275,7 +275,7 @@ public class ProxyInfo implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (!(o instanceof ProxyInfo)) return false; ProxyInfo p = (ProxyInfo)o; // If PAC URL is present in either then they must be equal. diff --git a/framework/src/android/net/RouteInfo.java b/framework/src/android/net/RouteInfo.java index 94f849f006..5b6684ace0 100644 --- a/framework/src/android/net/RouteInfo.java +++ b/framework/src/android/net/RouteInfo.java @@ -534,7 +534,7 @@ public final class RouteInfo implements Parcelable { * Compares this RouteInfo object against the specified object and indicates if they are equal. * @return {@code true} if the objects are equal, {@code false} otherwise. */ - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (!(obj instanceof RouteInfo)) return false; @@ -570,7 +570,7 @@ public final class RouteInfo implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (!(o instanceof RouteKey)) { return false; } From 3d7a74f4d0ee0ce72c623faedbb7c0cf5970c0be Mon Sep 17 00:00:00 2001 From: lucaslin Date: Sat, 6 Feb 2021 17:02:21 +0800 Subject: [PATCH 019/232] Expose TEST_TAP_PREFIX as system API TestNetworkManager is a part of connectivity mainline module and it is already a system API, but its constant - TEST_TAP_PREFIX is hidden, so expose it for the callers inside framework. Bug: 172183305 Test: make update-api Change-Id: I7fc08be4a6ca6325d5b576b761afda17865cefaa --- framework/src/android/net/TestNetworkManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java index 4e894143bf..a174a7be85 100644 --- a/framework/src/android/net/TestNetworkManager.java +++ b/framework/src/android/net/TestNetworkManager.java @@ -41,7 +41,6 @@ public class TestNetworkManager { /** * Prefix for tap interfaces created by this class. - * @hide */ public static final String TEST_TAP_PREFIX = "testtap"; From 7a0f31b944d9a7825d2db704dddba780f8b08d84 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 29 Jan 2021 20:18:03 +0900 Subject: [PATCH 020/232] More cleanly separate requests and listens. Currently, NetworkRequest has several types of requests (LISTEN, TRACK_DEFAULT, REQUEST, BACKGROUND_REQUEST), and we expect more to be added. There are really three categories of request: 1. Requests satisfied by only one network and will keep that network up, and thus need to be sent to NetworkProviders: REQUEST, BACKGROUND_REQUEST. 2. Requests satisfied by only one network but will not keep that network up: TRACK_DEFAULT 3. Requests satisfied by multiple networks and will not keep any networks up: LISTEN. Unfortunately the separation is not very clear. Currently, for any valid request, either isListen() will return true or isRequest() will return true. This makes it impossible to tell whether a particular request should be sent to NetworkProviders, so the current code sends TRACK_DEFAULT requests to NetworkProviders as well. This is incorrect - a TRACK_DEFAULT should never keep a network up, for example. This CL attempts to clarify things by making isRequest() return false for TRACK_DEFAULT requests and thus never sending them to NetworkProviders. After this CL: - isRequest will return true only for requests that attempt to bring up or keep up a network. - isListen will return true only for requests that match multiple networks but do not keep any of them up. - Neither will return true for TRACK_DEFAULT. Test: atest ConnectivityServiceTest Change-Id: I7aad30ade8f7ab2a179e53483d9afd8675f64a12 --- framework/src/android/net/NetworkRequest.java | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index c4d1b09a5c..b9ef4c21ef 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -435,25 +435,7 @@ public class NetworkRequest implements Parcelable { * @hide */ public boolean isRequest() { - return isForegroundRequest() || isBackgroundRequest(); - } - - /** - * Returns true iff. the contained NetworkRequest is one that: - * - * - should be associated with at most one satisfying network - * at a time; - * - * - should cause a network to be kept up and in the foreground if - * it is the best network which can satisfy the NetworkRequest. - * - * For full detail of how isRequest() is used for pairing Networks with - * NetworkRequests read rematchNetworkAndRequests(). - * - * @hide - */ - public boolean isForegroundRequest() { - return type == Type.TRACK_DEFAULT || type == Type.REQUEST; + return type == Type.REQUEST || type == Type.BACKGROUND_REQUEST; } /** From d9a569fd4a0e635a68d6f57e70c7f77449569c73 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 5 Feb 2021 01:10:56 +0900 Subject: [PATCH 021/232] Add to VpnManager the VPN APIs used by Settings and SystemUI. Settings and SystemUI use these through raw AIDL calls. Make them proper manager calls so we can move the implementation without touching the callers again. For now these still call into ConnectivityService via IConnectivityManager. In an upcoming CL the implementation will move to VpnManagerService. Test: m Bug: 173331190 Change-Id: I91528e1ad9948fbaa1fc5e37c61c5eb95f54964e --- framework/src/android/net/VpnManager.java | 105 +++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java index c87b8279c4..1812509ba6 100644 --- a/framework/src/android/net/VpnManager.java +++ b/framework/src/android/net/VpnManager.java @@ -21,6 +21,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.Activity; import android.content.ComponentName; import android.content.Context; @@ -28,6 +29,8 @@ import android.content.Intent; import android.content.res.Resources; import android.os.RemoteException; +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; import java.io.IOException; @@ -161,4 +164,104 @@ public class VpnManager { throw e.rethrowFromSystemServer(); } } -} + + /** + * Return the VPN configuration for the given user ID. + * @hide + */ + @Nullable + public VpnConfig getVpnConfig(@UserIdInt int userId) { + try { + return mService.getVpnConfig(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Prepare for a VPN application. + * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, + * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. + * + * @param oldPackage Package name of the application which currently controls VPN, which will + * be replaced. If there is no such application, this should should either be + * {@code null} or {@link VpnConfig.LEGACY_VPN}. + * @param newPackage Package name of the application which should gain control of VPN, or + * {@code null} to disable. + * @param userId User for whom to prepare the new VPN. + * + * @hide + */ + public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, + int userId) { + try { + return mService.prepareVpn(oldPackage, newPackage, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set whether the VPN package has the ability to launch VPNs without user intervention. This + * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} + * class. If the caller is not {@code userId}, {@link + * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. + * + * @param packageName The package for which authorization state should change. + * @param userId User for whom {@code packageName} is installed. + * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN + * permissions should be granted. When unauthorizing an app, {@link + * VpnManager.TYPE_VPN_NONE} should be used. + * @hide + */ + public void setVpnPackageAuthorization( + String packageName, int userId, @VpnManager.VpnType int vpnType) { + try { + mService.setVpnPackageAuthorization(packageName, userId, vpnType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the legacy VPN information for the specified user ID. + * @hide + */ + public LegacyVpnInfo getLegacyVpnInfo(@UserIdInt int userId) { + try { + return mService.getLegacyVpnInfo(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Starts a legacy VPN. + * @hide + */ + public void startLegacyVpn(VpnProfile profile) { + try { + mService.startLegacyVpn(profile); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Informs the service that legacy lockdown VPN state should be updated (e.g., if its keystore + * entry has been updated). If the LockdownVpn mechanism is enabled, updates the vpn + * with a reload of its profile. + * + *

This method can only be called by the system UID + * @return a boolean indicating success + * + * @hide + */ + public boolean updateLockdownVpn() { + try { + return mService.updateLockdownVpn(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} \ No newline at end of file From f151290e0c518ef4d28d4ce9349838e4a0897840 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Fri, 5 Feb 2021 17:33:53 +0800 Subject: [PATCH 022/232] Move shared Proxy method and constants to shared lib The static validate() method an PROXY_* constants in Proxy are used by both module and Settings. After Proxy is moving into connectivity module, Setting will not able to access them. Thus, move them to ProxyUtils in net shared lib. Bug: 172183305 Test: atest FrameworksNetTests Test: atest CtsNetTestCases:ProxyInfoTest Change-Id: I02c88aeaeb53d62b87a42a2d8ead9b140b054650 --- framework/src/android/net/Proxy.java | 60 ------------------------ framework/src/android/net/ProxyInfo.java | 4 +- 2 files changed, 3 insertions(+), 61 deletions(-) diff --git a/framework/src/android/net/Proxy.java b/framework/src/android/net/Proxy.java index 03b07e080a..9cd7ab2c3e 100644 --- a/framework/src/android/net/Proxy.java +++ b/framework/src/android/net/Proxy.java @@ -30,8 +30,6 @@ import java.net.InetSocketAddress; import java.net.ProxySelector; import java.net.URI; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * A convenience class for accessing the user and default proxy @@ -64,40 +62,9 @@ public final class Proxy { @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; - /** @hide */ - public static final int PROXY_VALID = 0; - /** @hide */ - public static final int PROXY_HOSTNAME_EMPTY = 1; - /** @hide */ - public static final int PROXY_HOSTNAME_INVALID = 2; - /** @hide */ - public static final int PROXY_PORT_EMPTY = 3; - /** @hide */ - public static final int PROXY_PORT_INVALID = 4; - /** @hide */ - public static final int PROXY_EXCLLIST_INVALID = 5; - private static ConnectivityManager sConnectivityManager = null; - // Hostname / IP REGEX validation - // Matches blank input, ips, and domain names - private static final String NAME_IP_REGEX = - "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*"; - - private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$"; - - private static final Pattern HOSTNAME_PATTERN; - - private static final String EXCL_REGEX = - "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*"; - - private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$"; - - private static final Pattern EXCLLIST_PATTERN; - static { - HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP); - EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); sDefaultProxySelector = ProxySelector.getDefault(); } @@ -216,33 +183,6 @@ public final class Proxy { return false; } - /** - * Validate syntax of hostname, port and exclusion list entries - * {@hide} - */ - public static int validate(String hostname, String port, String exclList) { - Matcher match = HOSTNAME_PATTERN.matcher(hostname); - Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList); - - if (!match.matches()) return PROXY_HOSTNAME_INVALID; - - if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID; - - if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY; - - if (port.length() > 0) { - if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY; - int portVal = -1; - try { - portVal = Integer.parseInt(port); - } catch (NumberFormatException ex) { - return PROXY_PORT_INVALID; - } - if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID; - } - return PROXY_VALID; - } - /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static final void setHttpProxySystemProperty(ProxyInfo p) { diff --git a/framework/src/android/net/ProxyInfo.java b/framework/src/android/net/ProxyInfo.java index c9bca2876b..fb37fbe492 100644 --- a/framework/src/android/net/ProxyInfo.java +++ b/framework/src/android/net/ProxyInfo.java @@ -23,6 +23,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.net.module.util.ProxyUtils; + import java.net.InetSocketAddress; import java.net.URLConnection; import java.util.List; @@ -233,7 +235,7 @@ public class ProxyInfo implements Parcelable { */ public boolean isValid() { if (!Uri.EMPTY.equals(mPacFileUrl)) return true; - return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, + return ProxyUtils.PROXY_VALID == ProxyUtils.validate(mHost == null ? "" : mHost, mPort == 0 ? "" : Integer.toString(mPort), mExclusionList == null ? "" : mExclusionList); } From a8a477b84e17475b225da9dec3418b953c3c8136 Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Thu, 17 Dec 2020 14:53:09 -0800 Subject: [PATCH 023/232] ConnectivityService: Plumb attribution tag for location permission checks Not currently setting the atttribution tag for location permission checks. Plumb the attribution tag for all location permision checks (so that location access is correctly attributed to individual components within an app) Bug: 162602799 Test: atest android.net Test: atest com.android.server Change-Id: Iee95f05204f51a4f8cb1f36acfb60e8cdeb156f4 --- framework/src/android/net/ConnectivityManager.java | 13 ++++++++----- framework/src/android/net/IConnectivityManager.aidl | 11 +++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index fef41522bd..d04a5bee51 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1368,7 +1368,7 @@ public class ConnectivityManager { public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) { try { return mService.getDefaultNetworkCapabilitiesForUser( - userId, mContext.getOpPackageName()); + userId, mContext.getOpPackageName(), getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1450,7 +1450,8 @@ public class ConnectivityManager { @Nullable public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) { try { - return mService.getNetworkCapabilities(network, mContext.getOpPackageName()); + return mService.getNetworkCapabilities( + network, mContext.getOpPackageName(), getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2142,7 +2143,7 @@ public class ConnectivityManager { */ // TODO: Remove method and replace with direct call once R code is pushed to AOSP private @Nullable String getAttributionTag() { - return null; + return mContext.getAttributionTag(); } /** @@ -3735,7 +3736,8 @@ public class ConnectivityManager { Binder binder = new Binder(); if (reqType == LISTEN) { request = mService.listenForNetwork( - need, messenger, binder, callingPackageName); + need, messenger, binder, callingPackageName, + getAttributionTag()); } else { request = mService.requestNetwork( need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType, @@ -4180,7 +4182,8 @@ public class ConnectivityManager { checkPendingIntentNotNull(operation); try { mService.pendingListenForNetwork( - request.networkCapabilities, operation, mContext.getOpPackageName()); + request.networkCapabilities, operation, mContext.getOpPackageName(), + getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (ServiceSpecificException e) { diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index e2672c480c..f909d13625 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -66,7 +66,7 @@ interface IConnectivityManager Network getNetworkForType(int networkType); Network[] getAllNetworks(); NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser( - int userId, String callingPackageName); + int userId, String callingPackageName, String callingAttributionTag); boolean isNetworkSupported(int networkType); @@ -75,7 +75,8 @@ interface IConnectivityManager LinkProperties getLinkPropertiesForType(int networkType); LinkProperties getLinkProperties(in Network network); - NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName); + NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName, + String callingAttributionTag); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) NetworkState[] getAllNetworkState(); @@ -176,10 +177,12 @@ interface IConnectivityManager void releasePendingNetworkRequest(in PendingIntent operation); NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, in IBinder binder, String callingPackageName); + in Messenger messenger, in IBinder binder, String callingPackageName, + String callingAttributionTag); void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, - in PendingIntent operation, String callingPackageName); + in PendingIntent operation, String callingPackageName, + String callingAttributionTag); void releaseNetworkRequest(in NetworkRequest networkRequest); From 7c85e7d4d67fe02573666d201b54c30d2327f0f3 Mon Sep 17 00:00:00 2001 From: Hai Shalom Date: Thu, 4 Feb 2021 19:34:06 -0800 Subject: [PATCH 024/232] Support for Terms & Conditions notification - Added API to add T&C URL in the CaptivePortalData class, and to indicate if the source is from Passpoint. - Added source indication for the Venue URL API. - Allow the connectivity service to send a new T&C acceptance notification. - Updated the merge method to prefer the Capport data over the network agent data, if the source is not authenticated (not from Passpoint). - Propagate the Venue Friendly name to the captive portal activity to be used instead of SSID, when available. Bug: 162785447 Test: End-to-end test Test: atest ConnectivityServiceTest Test: atest CtsNetTestCasesLatestSdk:CaptivePortalDataTest Test: atest NetworkNotificationManagerTest Change-Id: I4e77c3b6c01941b03c46ad32da70c77e0fecac64 --- .../src/android/net/CaptivePortalData.java | 90 +++++++++++++++++-- 1 file changed, 82 insertions(+), 8 deletions(-) diff --git a/framework/src/android/net/CaptivePortalData.java b/framework/src/android/net/CaptivePortalData.java index 18467fad8e..f4b46e9f11 100644 --- a/framework/src/android/net/CaptivePortalData.java +++ b/framework/src/android/net/CaptivePortalData.java @@ -16,12 +16,15 @@ package android.net; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -40,10 +43,29 @@ public final class CaptivePortalData implements Parcelable { private final long mExpiryTimeMillis; private final boolean mCaptive; private final String mVenueFriendlyName; + private final int mVenueInfoUrlSource; + private final int mTermsAndConditionsSource; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"CAPTIVE_PORTAL_DATA_SOURCE_"}, value = { + CAPTIVE_PORTAL_DATA_SOURCE_OTHER, + CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT}) + public @interface CaptivePortalDataSource {} + + /** + * Source of information: Other (default) + */ + public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; + + /** + * Source of information: Wi-Fi Passpoint + */ + public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive, - String venueFriendlyName) { + String venueFriendlyName, int venueInfoUrlSource, int termsAndConditionsSource) { mRefreshTimeMillis = refreshTimeMillis; mUserPortalUrl = userPortalUrl; mVenueInfoUrl = venueInfoUrl; @@ -52,11 +74,14 @@ public final class CaptivePortalData implements Parcelable { mExpiryTimeMillis = expiryTimeMillis; mCaptive = captive; mVenueFriendlyName = venueFriendlyName; + mVenueInfoUrlSource = venueInfoUrlSource; + mTermsAndConditionsSource = termsAndConditionsSource; } private CaptivePortalData(Parcel p) { this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(), - p.readLong(), p.readLong(), p.readBoolean(), p.readString()); + p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(), + p.readInt()); } @Override @@ -74,6 +99,8 @@ public final class CaptivePortalData implements Parcelable { dest.writeLong(mExpiryTimeMillis); dest.writeBoolean(mCaptive); dest.writeString(mVenueFriendlyName); + dest.writeInt(mVenueInfoUrlSource); + dest.writeInt(mTermsAndConditionsSource); } /** @@ -88,6 +115,9 @@ public final class CaptivePortalData implements Parcelable { private long mExpiryTime = -1; private boolean mCaptive; private String mVenueFriendlyName; + private @CaptivePortalDataSource int mVenueInfoUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER; + private @CaptivePortalDataSource int mUserPortalUrlSource = + CAPTIVE_PORTAL_DATA_SOURCE_OTHER; /** * Create an empty builder. @@ -100,8 +130,8 @@ public final class CaptivePortalData implements Parcelable { public Builder(@Nullable CaptivePortalData data) { if (data == null) return; setRefreshTime(data.mRefreshTimeMillis) - .setUserPortalUrl(data.mUserPortalUrl) - .setVenueInfoUrl(data.mVenueInfoUrl) + .setUserPortalUrl(data.mUserPortalUrl, data.mTermsAndConditionsSource) + .setVenueInfoUrl(data.mVenueInfoUrl, data.mVenueInfoUrlSource) .setSessionExtendable(data.mIsSessionExtendable) .setBytesRemaining(data.mByteLimit) .setExpiryTime(data.mExpiryTimeMillis) @@ -123,7 +153,18 @@ public final class CaptivePortalData implements Parcelable { */ @NonNull public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) { + return setUserPortalUrl(userPortalUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER); + } + + /** + * Set the URL to be used for users to login to the portal, if captive, and the source of + * the data, see {@link CaptivePortalDataSource} + */ + @NonNull + public Builder setUserPortalUrl(@Nullable Uri userPortalUrl, + @CaptivePortalDataSource int source) { mUserPortalUrl = userPortalUrl; + mUserPortalUrlSource = source; return this; } @@ -132,7 +173,18 @@ public final class CaptivePortalData implements Parcelable { */ @NonNull public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) { + return setVenueInfoUrl(venueInfoUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER); + } + + /** + * Set the URL that can be used by users to view information about the network venue, and + * the source of the data, see {@link CaptivePortalDataSource} + */ + @NonNull + public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl, + @CaptivePortalDataSource int source) { mVenueInfoUrl = venueInfoUrl; + mVenueInfoUrlSource = source; return this; } @@ -188,7 +240,8 @@ public final class CaptivePortalData implements Parcelable { public CaptivePortalData build() { return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl, mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive, - mVenueFriendlyName); + mVenueFriendlyName, mVenueInfoUrlSource, + mUserPortalUrlSource); } } @@ -248,6 +301,22 @@ public final class CaptivePortalData implements Parcelable { return mCaptive; } + /** + * Get the information source of the Venue URL + * @return The source that the Venue URL was obtained from + */ + public @CaptivePortalDataSource int getVenueInfoUrlSource() { + return mVenueInfoUrlSource; + } + + /** + * Get the information source of the user portal URL + * @return The source that the user portal URL was obtained from + */ + public @CaptivePortalDataSource int getUserPortalUrlSource() { + return mTermsAndConditionsSource; + } + /** * Get the venue friendly name */ @@ -272,11 +341,12 @@ public final class CaptivePortalData implements Parcelable { @Override public int hashCode() { return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl, - mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName); + mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName, + mVenueInfoUrlSource, mTermsAndConditionsSource); } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (!(obj instanceof CaptivePortalData)) return false; final CaptivePortalData other = (CaptivePortalData) obj; return mRefreshTimeMillis == other.mRefreshTimeMillis @@ -286,7 +356,9 @@ public final class CaptivePortalData implements Parcelable { && mByteLimit == other.mByteLimit && mExpiryTimeMillis == other.mExpiryTimeMillis && mCaptive == other.mCaptive - && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName); + && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName) + && mVenueInfoUrlSource == other.mVenueInfoUrlSource + && mTermsAndConditionsSource == other.mTermsAndConditionsSource; } @Override @@ -300,6 +372,8 @@ public final class CaptivePortalData implements Parcelable { + ", expiryTime: " + mExpiryTimeMillis + ", captive: " + mCaptive + ", venueFriendlyName: " + mVenueFriendlyName + + ", venueInfoUrlSource: " + mVenueInfoUrlSource + + ", termsAndConditionsSource: " + mTermsAndConditionsSource + "}"; } } From a77d05ec0afa867a64115431b7ec9370db5660ef Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 29 Jan 2021 20:14:04 +0900 Subject: [PATCH 025/232] Add a registerSystemDefaultNetworkCallback method. This method allows internal components to track the system default network. This differs from registerDefaultNetworkCallback because that method sends callbacks for the default network for the UID that called it. This may not be the system default network, for example, when a VPN is up and applies to the UID. Bug: 173331190 Test: new unit tests in ConnectivityServiceTest Test: new unit tests in ConnectivityManagerTest Test: new CTS tests in ConnectivityServiceTest Test: new CTS tests in HostsideVpnTests in other CL in this topic Change-Id: Id02748a2183f71b71ff2a53a580466b9dcecaa93 --- .../src/android/net/ConnectivityManager.java | 66 +++++++++++++++---- framework/src/android/net/NetworkRequest.java | 22 +++---- 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index d04a5bee51..0976b753e6 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -21,6 +21,7 @@ import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.REQUEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; import android.annotation.CallbackExecutor; @@ -3721,7 +3722,8 @@ public class ConnectivityManager { printStackTrace(); checkCallbackNotNull(callback); Preconditions.checkArgument( - reqType == TRACK_DEFAULT || need != null, "null NetworkCapabilities"); + reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null, + "null NetworkCapabilities"); final NetworkRequest request; final String callingPackageName = mContext.getOpPackageName(); try { @@ -4192,8 +4194,9 @@ public class ConnectivityManager { } /** - * Registers to receive notifications about changes in the system default network. The callbacks - * will continue to be called until either the application exits or + * Registers to receive notifications about changes in the application's default network. This + * may be a physical network or a virtual network, such as a VPN that applies to the + * application. The callbacks will continue to be called until either the application exits or * {@link #unregisterNetworkCallback(NetworkCallback)} is called. * *

To avoid performance issues due to apps leaking callbacks, the system will limit the @@ -4206,7 +4209,7 @@ public class ConnectivityManager { * {@link #unregisterNetworkCallback(NetworkCallback)}. * * @param networkCallback The {@link NetworkCallback} that the system will call as the - * system default network changes. + * application's default network changes. * The callback is invoked on the default internal Handler. * @throws RuntimeException if the app already has too many callbacks registered. */ @@ -4215,11 +4218,47 @@ public class ConnectivityManager { registerDefaultNetworkCallback(networkCallback, getDefaultHandler()); } + /** + * Registers to receive notifications about changes in the application's default network. This + * may be a physical network or a virtual network, such as a VPN that applies to the + * application. The callbacks will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * application's default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, + @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, + TRACK_DEFAULT, TYPE_NONE, cbHandler); + } + /** * Registers to receive notifications about changes in the system default network. The callbacks * will continue to be called until either the application exits or * {@link #unregisterNetworkCallback(NetworkCallback)} is called. * + * This method should not be used to determine networking state seen by applications, because in + * many cases, most or even all application traffic may not use the default network directly, + * and traffic from different applications may go on different networks by default. As an + * example, if a VPN is connected, traffic from all applications might be sent through the VPN + * and not onto the system default network. Applications or system components desiring to do + * determine network state as seen by applications should use other methods such as + * {@link #registerDefaultNetworkCallback(NetworkCallback, Handler)}. + * *

To avoid performance issues due to apps leaking callbacks, the system will limit the * number of outstanding requests to 100 per app (identified by their UID), shared with * all variants of this method, of {@link #requestNetwork} as well as @@ -4233,20 +4272,19 @@ public class ConnectivityManager { * system default network changes. * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. * @throws RuntimeException if the app already has too many callbacks registered. + * + * @hide */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint({"ExecutorRegistration", "PairedRegistration"}) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, @NonNull Handler handler) { - // This works because if the NetworkCapabilities are null, - // ConnectivityService takes them from the default request. - // - // Since the capabilities are exactly the same as the default request's - // capabilities, this request is guaranteed, at all times, to be - // satisfied by the same network, if any, that satisfies the default - // request, i.e., the system default network. CallbackHandler cbHandler = new CallbackHandler(handler); sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, - TRACK_DEFAULT, TYPE_NONE, cbHandler); + TRACK_SYSTEM_DEFAULT, TYPE_NONE, cbHandler); } /** diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 6540397d62..b4a651c060 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -104,17 +104,14 @@ public class NetworkRequest implements Parcelable { * callbacks about the single, highest scoring current network * (if any) that matches the specified NetworkCapabilities, or * - * - TRACK_DEFAULT, a hybrid of the two designed such that the - * framework will issue callbacks for the single, highest scoring - * current network (if any) that matches the capabilities of the - * default Internet request (mDefaultRequest), but which cannot cause - * the framework to either create or retain the existence of any - * specific network. Note that from the point of view of the request - * matching code, TRACK_DEFAULT is identical to REQUEST: its special - * behaviour is not due to different semantics, but to the fact that - * the system will only ever create a TRACK_DEFAULT with capabilities - * that are identical to the default request's capabilities, thus - * causing it to share fate in every way with the default request. + * - TRACK_DEFAULT, which causes the framework to issue callbacks for + * the single, highest scoring current network (if any) that will + * be chosen for an app, but which cannot cause the framework to + * either create or retain the existence of any specific network. + * + * - TRACK_SYSTEM_DEFAULT, which causes the framework to send callbacks + * for the network (if any) that satisfies the default Internet + * request. * * - BACKGROUND_REQUEST, like REQUEST but does not cause any networks * to retain the NET_CAPABILITY_FOREGROUND capability. A network with @@ -137,6 +134,7 @@ public class NetworkRequest implements Parcelable { TRACK_DEFAULT, REQUEST, BACKGROUND_REQUEST, + TRACK_SYSTEM_DEFAULT, }; /** @@ -601,6 +599,8 @@ public class NetworkRequest implements Parcelable { return NetworkRequestProto.TYPE_REQUEST; case BACKGROUND_REQUEST: return NetworkRequestProto.TYPE_BACKGROUND_REQUEST; + case TRACK_SYSTEM_DEFAULT: + return NetworkRequestProto.TYPE_TRACK_SYSTEM_DEFAULT; default: return NetworkRequestProto.TYPE_UNKNOWN; } From 026fbb8a1fd6abfe69a135d4654c7701396a6f7e Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Wed, 3 Feb 2021 03:12:15 +0900 Subject: [PATCH 026/232] Add a VpnTransportInfo object. This currently stores the VPN type and a session name, but can be extended in the future. Bug: 173331190 Test: added coverage in VpnTest Test: added coverage in ConnectivityServiceTest Test: added coverage in NetworkAgentTest Change-Id: I450858a9fa332c8d896dbdb4c14337d5ec23677f --- .../src/android/net/NetworkCapabilities.java | 2 ++ framework/src/android/net/VpnManager.java | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 55b2c3c9e1..9d67f0b843 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -762,12 +762,14 @@ public final class NetworkCapabilities implements Parcelable { final int originalSignalStrength = mSignalStrength; final int originalOwnerUid = getOwnerUid(); final int[] originalAdministratorUids = getAdministratorUids(); + final TransportInfo originalTransportInfo = getTransportInfo(); clearAll(); mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS) | (1 << TRANSPORT_TEST); mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES; mNetworkSpecifier = originalSpecifier; mSignalStrength = originalSignalStrength; + mTransportInfo = originalTransportInfo; // Only retain the owner and administrator UIDs if they match the app registering the remote // caller that registered the network. diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java index 1812509ba6..1e30283a9e 100644 --- a/framework/src/android/net/VpnManager.java +++ b/framework/src/android/net/VpnManager.java @@ -55,13 +55,29 @@ import java.security.GeneralSecurityException; public class VpnManager { /** Type representing a lack of VPN @hide */ public static final int TYPE_VPN_NONE = -1; - /** VPN service type code @hide */ + + /** + * A VPN created by an app using the {@link VpnService} API. + * @hide + */ public static final int TYPE_VPN_SERVICE = 1; - /** Platform VPN type code @hide */ + + /** + * A VPN created using a {@link VpnManager} API such as {@link #startProvisionedVpnProfile}. + * @hide + */ public static final int TYPE_VPN_PLATFORM = 2; + /** + * An IPsec VPN created by the built-in LegacyVpnRunner. + * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead. + * @hide + */ + @Deprecated + public static final int TYPE_VPN_LEGACY = 3; + /** @hide */ - @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM}) + @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY}) @Retention(RetentionPolicy.SOURCE) public @interface VpnType {} From c71cff88374c7f0fd5904ffce064caa5993657be Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 15 Jan 2021 01:29:01 +0900 Subject: [PATCH 027/232] Convert LockdownVpnTracker to NetworkCallbacks. This will allow moving LockdownVpnTracker from the connectivity to the VPN code. This requires moderate refactoring since it's pretty tightly coupled to both. In this CL: 1. Add an @hide API to tell ConnectivityService that legacy lockdown VPN is enabled. I chose not to use the existing setVpnRequiredForUids API because that method has specific semantics and because it will be required long term since it's used by non-legacy VPN types. 2. Instead of updating LockdownVpnTracker inline from the ConnectivityService handler thread, have it listen to NetworkCallbacks. This introduces an extra thread hop, but most of the interactions between the lockdown VPN and CS were via NetworkAgent, which is asynchronous anyway. 3. Add code to LegacyTypeTracker to send the extra CONNECTIVITY_ACTION broadcast for the underlying network type that is sent after the VPN connects. In order to do this, make Make LockdownVpnTracker specify its underlying network (via setUnderlyingNetworks) when it connects. 4. Reimplement LockdownVpnTracker#augmentNetworkInfo based on information that is available in ConnectivityService. 5. Remove the code in LockdownVpnTracker that counted errors. I think this code has not worked since lollipop, because ConnectivityService never sees NetworkInfo objects in state FAILED. This is because ConnectivityService only hears about NetworkInfo objects via NetworkAgents, and LegacyVpnRunner only registers its NetworkAgent when the connection succeeds. Bug: 173331190 Test: passes existing tests in ConnectivityServiceTest Change-Id: I66d18512882efd468ee0ecec61f28786a195b357 --- .../src/android/net/ConnectivityManager.java | 39 +++++++++++++++++++ .../src/android/net/IConnectivityManager.aidl | 1 + 2 files changed, 40 insertions(+) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 0976b753e6..8437798b7b 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1220,6 +1220,45 @@ public class ConnectivityManager { } } + /** + * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by + * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12 + * but is still supported for backwards compatibility. + *

+ * This type of VPN is assumed always to use the system default network, and must always declare + * exactly one underlying network, which is the network that was the default when the VPN + * connected. + *

+ * Calling this method with {@code true} enables legacy behaviour, specifically: + *

    + *
  • Any VPN that applies to userId 0 behaves specially with respect to deprecated + * {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the + * {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN + * connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network + * underlying the VPN.
  • + *
  • Deprecated APIs that return {@link NetworkInfo} objects will have their state + * similarly replaced by the VPN network state.
  • + *
  • Information on current network interfaces passed to NetworkStatsService will not + * include any VPN interfaces.
  • + *
+ * + * @param enabled whether legacy lockdown VPN is enabled or disabled + * + * TODO: @SystemApi(client = MODULE_LIBRARIES) + * + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void setLegacyLockdownVpnEnabled(boolean enabled) { + try { + mService.setLegacyLockdownVpnEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Returns details about the currently active default data network * for a given uid. This is for internal use only to avoid spying diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index f909d13625..ab134eb6d2 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -151,6 +151,7 @@ interface IConnectivityManager boolean isVpnLockdownEnabled(int userId); List getVpnLockdownWhitelist(int userId); void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); + void setLegacyLockdownVpnEnabled(boolean enabled); void setProvisioningNotificationVisible(boolean visible, int networkType, in String action); From d97b404c487fd27b8b0a4d74a9fe8ff816a03f86 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 9 Nov 2020 10:34:03 +0900 Subject: [PATCH 028/232] Add a skeleton VpnManagerService, and start it on boot. This adds a lot of unused code but this should make it easier to review subsequent CLs. Bug: 173331190 Test: builds, boots, "dumpsys vpnmanager" succeeds Change-Id: Ied379654a0c3ab6242d3125661fe30f322395059 --- framework/src/android/net/VpnManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java index 1e30283a9e..a65769cc72 100644 --- a/framework/src/android/net/VpnManager.java +++ b/framework/src/android/net/VpnManager.java @@ -76,6 +76,12 @@ public class VpnManager { @Deprecated public static final int TYPE_VPN_LEGACY = 3; + /** + * Channel for VPN notifications. + * @hide + */ + public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; + /** @hide */ @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY}) @Retention(RetentionPolicy.SOURCE) From 842075ed8e87a6936f29476070dcb39addc4e7f0 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 4 Feb 2021 17:32:07 +0900 Subject: [PATCH 029/232] Move VPN code from ConnectivityService to VpnManagerService. ConnectivityService itself does not depend on mVpns or the Vpn class any more. Most of this CL is simply moving code from one class to another: - Move the AIDL calls from IConnectivityManager to IVpnManager. - Move the implementation from ConnectivityService to the new VpnManagerService. - Move the APIs from ConnectivityManager to VpnManager, but temporarily maintain some shims in ConnectivityManager for the moved calls so that existing callers do not have to be modified in this CL. - Update VpnService to call IVpnManager instead of IConnectivityManager. - Move the code that registers the VpnManager service from ConnectivityFrameworkInitializer to SystemServiceRegistry. Bug: 173331190 Test: atest HostsideVpnTests FrameworksNetTests CtsNetTestCases Change-Id: I4911e2144df721a94fa00da9edf0dc372a7091c2 --- .../net/ConnectivityFrameworkInitializer.java | 11 -- .../src/android/net/ConnectivityManager.java | 129 ++++++------------ .../src/android/net/IConnectivityManager.aidl | 37 ----- framework/src/android/net/VpnManager.java | 123 ++++++++++++++++- framework/src/android/net/VpnService.java | 17 ++- 5 files changed, 167 insertions(+), 150 deletions(-) diff --git a/framework/src/android/net/ConnectivityFrameworkInitializer.java b/framework/src/android/net/ConnectivityFrameworkInitializer.java index 9afa5d1311..92a792b784 100644 --- a/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -49,17 +49,6 @@ public final class ConnectivityFrameworkInitializer { } ); - // TODO: move outside of the connectivity JAR - SystemServiceRegistry.registerContextAwareService( - Context.VPN_MANAGEMENT_SERVICE, - VpnManager.class, - (context) -> { - final ConnectivityManager cm = context.getSystemService( - ConnectivityManager.class); - return cm.createVpnManager(); - } - ); - SystemServiceRegistry.registerContextAwareService( Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, ConnectivityDiagnosticsManager.class, diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 8437798b7b..25b6460ddf 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -824,6 +824,7 @@ public class ConnectivityManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; + /** * A kludge to facilitate static access where a Context pointer isn't available, like in the * case of the static set/getProcessDefaultNetwork methods and from the Network class. @@ -1069,106 +1070,55 @@ public class ConnectivityManager { } /** - * Checks if a VPN app supports always-on mode. - * - * In order to support the always-on feature, an app has to - *
    - *
  • target {@link VERSION_CODES#N API 24} or above, and - *
  • not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} - * meta-data field. - *
- * - * @param userId The identifier of the user for whom the VPN app is installed. - * @param vpnPackage The canonical package name of the VPN app. - * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ + @Deprecated public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - try { - return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); } /** - * Configures an always-on VPN connection through a specific application. - * This connection is automatically granted and persisted after a reboot. - * - *

The designated package should declare a {@link VpnService} in its - * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, - * otherwise the call will fail. - * - * @param userId The identifier of the user to set an always-on VPN for. - * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} - * to remove an existing always-on VPN configuration. - * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. - * @param lockdownAllowlist The list of packages that are allowed to access network directly - * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so - * this method must be called when a package that should be allowed is installed or - * uninstalled. - * @return {@code true} if the package is set as always-on VPN controller; - * {@code false} otherwise. + * Calls VpnManager#setAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, boolean lockdownEnabled, @Nullable List lockdownAllowlist) { - try { - return mService.setAlwaysOnVpnPackage( - userId, vpnPackage, lockdownEnabled, lockdownAllowlist); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, + lockdownAllowlist); } - /** - * Returns the package name of the currently set always-on VPN application. - * If there is no always-on VPN set, or the VPN is provided by the system instead - * of by an app, {@code null} will be returned. - * - * @return Package name of VPN controller responsible for always-on VPN, - * or {@code null} if none is set. + /** + * Calls VpnManager#getAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public String getAlwaysOnVpnPackageForUser(int userId) { - try { - return mService.getAlwaysOnVpnPackage(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().getAlwaysOnVpnPackageForUser(userId); } /** - * @return whether always-on VPN is in lockdown mode. - * + * Calls VpnManager#isVpnLockdownEnabled. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + */ + @Deprecated public boolean isVpnLockdownEnabled(int userId) { - try { - return mService.isVpnLockdownEnabled(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - + return getVpnManager().isVpnLockdownEnabled(userId); } /** - * @return the list of packages that are allowed to access network when always-on VPN is in - * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. - * + * Calls VpnManager#getVpnLockdownAllowlist. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + */ + @Deprecated public List getVpnLockdownWhitelist(int userId) { - try { - return mService.getVpnLockdownWhitelist(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().getVpnLockdownAllowlist(userId); } /** @@ -3219,20 +3169,13 @@ public class ConnectivityManager { } /** - * If the LockdownVpn mechanism is enabled, updates the vpn - * with a reload of its profile. - * - * @return a boolean with {@code} indicating success - * - *

This method can only be called by the system UID - * {@hide} + * Calls VpnManager#updateLockdownVpn. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide */ + @Deprecated public boolean updateLockdownVpn() { - try { - return mService.updateLockdownVpn(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().updateLockdownVpn(); } /** @@ -4596,6 +4539,8 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); + // TODO: Migrate callers to VpnManager#factoryReset. + getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4889,9 +4834,13 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } - /** @hide */ - public VpnManager createVpnManager() { - return new VpnManager(mContext, mService); + /** + * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a + * private final mVpnManager because ConnectivityManager is initialized before VpnManager. + * @hide TODO: remove. + */ + public VpnManager getVpnManager() { + return mContext.getSystemService(VpnManager.class); } /** @hide */ diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index ab134eb6d2..77aee5ec6f 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -42,9 +42,6 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import com.android.connectivity.aidl.INetworkAgent; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; /** * Interface that answers queries about, and allows changing, the @@ -122,34 +119,6 @@ interface IConnectivityManager ProxyInfo getProxyForNetwork(in Network nework); - boolean prepareVpn(String oldPackage, String newPackage, int userId); - - void setVpnPackageAuthorization(String packageName, int userId, int vpnType); - - ParcelFileDescriptor establishVpn(in VpnConfig config); - - boolean provisionVpnProfile(in VpnProfile profile, String packageName); - - void deleteVpnProfile(String packageName); - - void startVpnProfile(String packageName); - - void stopVpnProfile(String packageName); - - VpnConfig getVpnConfig(int userId); - - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - void startLegacyVpn(in VpnProfile profile); - - LegacyVpnInfo getLegacyVpnInfo(int userId); - - boolean updateLockdownVpn(); - boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); - boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, - in List lockdownWhitelist); - String getAlwaysOnVpnPackage(int userId); - boolean isVpnLockdownEnabled(int userId); - List getVpnLockdownWhitelist(int userId); void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); void setLegacyLockdownVpnEnabled(boolean enabled); @@ -200,10 +169,6 @@ interface IConnectivityManager int getRestoreDefaultNetworkDelay(int networkType); - boolean addVpnAddress(String address, int prefixLength); - boolean removeVpnAddress(String address, int prefixLength); - boolean setUnderlyingNetworksForVpn(in Network[] networks); - void factoryReset(); void startNattKeepalive(in Network network, int intervalSeconds, @@ -223,8 +188,6 @@ interface IConnectivityManager byte[] getNetworkWatchlistConfigHash(); int getConnectionOwnerUid(in ConnectionInfo connectionInfo); - boolean isCallerCurrentAlwaysOnVpnApp(); - boolean isCallerCurrentAlwaysOnVpnLockdownApp(); void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, in NetworkRequest request, String callingPackageName); diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java index a65769cc72..f472ed4381 100644 --- a/framework/src/android/net/VpnManager.java +++ b/framework/src/android/net/VpnManager.java @@ -21,6 +21,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.Activity; import android.content.ComponentName; @@ -37,6 +38,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.GeneralSecurityException; +import java.util.List; /** * This class provides an interface for apps to manage platform VPN profiles @@ -88,7 +90,7 @@ public class VpnManager { public @interface VpnType {} @NonNull private final Context mContext; - @NonNull private final IConnectivityManager mService; + @NonNull private final IVpnManager mService; private static Intent getIntentForConfirmation() { final Intent intent = new Intent(); @@ -107,9 +109,9 @@ public class VpnManager { * * @hide */ - public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) { + public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) { mContext = checkNotNull(ctx, "missing Context"); - mService = checkNotNull(service, "missing IConnectivityManager"); + mService = checkNotNull(service, "missing IVpnManager"); } /** @@ -200,6 +202,19 @@ public class VpnManager { } } + /** + * Resets all VPN settings back to factory defaults. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void factoryReset() { + try { + mService.factoryReset(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Prepare for a VPN application. * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, @@ -245,6 +260,108 @@ public class VpnManager { } } + /** + * Checks if a VPN app supports always-on mode. + * + * In order to support the always-on feature, an app has to + *

    + *
  • target {@link VERSION_CODES#N API 24} or above, and + *
  • not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} + * meta-data field. + *
+ * + * @param userId The identifier of the user for whom the VPN app is installed. + * @param vpnPackage The canonical package name of the VPN app. + * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * @hide + */ + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + try { + return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + *

The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @param lockdownAllowlist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be allowed is installed or + * uninstalled. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List lockdownAllowlist) { + try { + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownAllowlist); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the package name of the currently set always-on VPN application. + * If there is no always-on VPN set, or the VPN is provided by the system instead + * of by an app, {@code null} will be returned. + * + * @return Package name of VPN controller responsible for always-on VPN, + * or {@code null} if none is set. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public String getAlwaysOnVpnPackageForUser(int userId) { + try { + return mService.getAlwaysOnVpnPackage(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List getVpnLockdownAllowlist(int userId) { + try { + return mService.getVpnLockdownAllowlist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Return the legacy VPN information for the specified user ID. * @hide diff --git a/framework/src/android/net/VpnService.java b/framework/src/android/net/VpnService.java index 8e90a119fe..e43b0b6fa6 100644 --- a/framework/src/android/net/VpnService.java +++ b/framework/src/android/net/VpnService.java @@ -170,12 +170,11 @@ public class VpnService extends Service { "android.net.VpnService.SUPPORTS_ALWAYS_ON"; /** - * Use IConnectivityManager since those methods are hidden and not - * available in ConnectivityManager. + * Use IVpnManager since those methods are hidden and not available in VpnManager. */ - private static IConnectivityManager getService() { - return IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + private static IVpnManager getService() { + return IVpnManager.Stub.asInterface( + ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE)); } /** @@ -226,15 +225,15 @@ public class VpnService extends Service { @SystemApi @RequiresPermission(android.Manifest.permission.CONTROL_VPN) public static void prepareAndAuthorize(Context context) { - IConnectivityManager cm = getService(); + IVpnManager vm = getService(); String packageName = context.getPackageName(); try { // Only prepare if we're not already prepared. int userId = context.getUserId(); - if (!cm.prepareVpn(packageName, null, userId)) { - cm.prepareVpn(null, packageName, userId); + if (!vm.prepareVpn(packageName, null, userId)) { + vm.prepareVpn(null, packageName, userId); } - cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); + vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); } catch (RemoteException e) { // ignore } From b3d24768eea2ebfa1e32d949c172a884a0d00dc1 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 12 Feb 2021 04:09:15 +0000 Subject: [PATCH 030/232] Revert "[VCN07] Bypass VCN for non-internet app accessible cellu..." Revert submission 1579872-vcn07 Reason for revert: Possible culprit for b/179768816, broken ConnectivityControllerTest Reverted Changes: Id09b19c13:Address comments on aosp/1550755 I245bd69e0:[VCN07.1] Add test for bypassing VCN for non-inter... I9936894b9:[VCN07] Bypass VCN for non-internet app accessible... Change-Id: Ie5f8c3051fed40623e5d4705ac2243ef51a898bd --- framework/src/android/net/NetworkRequest.java | 69 ------------------- 1 file changed, 69 deletions(-) diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 9883692e7d..04011fc681 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -16,22 +16,6 @@ package android.net; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -46,8 +30,6 @@ import android.os.Process; import android.text.TextUtils; import android.util.proto.ProtoOutputStream; -import java.util.Arrays; -import java.util.List; import java.util.Objects; import java.util.Set; @@ -174,30 +156,8 @@ public class NetworkRequest implements Parcelable { * needed in terms of {@link NetworkCapabilities} features */ public static class Builder { - /** - * Capabilities that are currently compatible with VCN networks. - */ - private static final List VCN_SUPPORTED_CAPABILITIES = Arrays.asList( - NET_CAPABILITY_CAPTIVE_PORTAL, - NET_CAPABILITY_DUN, - NET_CAPABILITY_FOREGROUND, - NET_CAPABILITY_INTERNET, - NET_CAPABILITY_NOT_CONGESTED, - NET_CAPABILITY_NOT_METERED, - NET_CAPABILITY_NOT_RESTRICTED, - NET_CAPABILITY_NOT_ROAMING, - NET_CAPABILITY_NOT_SUSPENDED, - NET_CAPABILITY_NOT_VPN, - NET_CAPABILITY_PARTIAL_CONNECTIVITY, - NET_CAPABILITY_TEMPORARILY_NOT_METERED, - NET_CAPABILITY_TRUSTED, - NET_CAPABILITY_VALIDATED); - private final NetworkCapabilities mNetworkCapabilities; - // A boolean that represents the user modified NOT_VCN_MANAGED capability. - private boolean mModifiedNotVcnManaged = false; - /** * Default constructor for Builder. */ @@ -219,7 +179,6 @@ public class NetworkRequest implements Parcelable { // maybeMarkCapabilitiesRestricted() doesn't add back. final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities); nc.maybeMarkCapabilitiesRestricted(); - deduceNotVcnManagedCapability(nc); return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE, ConnectivityManager.REQUEST_ID_UNSET, Type.NONE); } @@ -236,9 +195,6 @@ public class NetworkRequest implements Parcelable { */ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addCapability(capability); - if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { - mModifiedNotVcnManaged = true; - } return this; } @@ -250,9 +206,6 @@ public class NetworkRequest implements Parcelable { */ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeCapability(capability); - if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { - mModifiedNotVcnManaged = true; - } return this; } @@ -310,9 +263,6 @@ public class NetworkRequest implements Parcelable { @NonNull public Builder clearCapabilities() { mNetworkCapabilities.clearAll(); - // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities - // should not be add back later. - mModifiedNotVcnManaged = true; return this; } @@ -432,25 +382,6 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setSignalStrength(signalStrength); return this; } - - /** - * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities - * and user intention, which includes: - * 1. For the requests that don't have anything besides - * {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to - * allow the callers automatically utilize VCN networks if available. - * 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED, - * do not alter them to allow user fire request that suits their need. - * - * @hide - */ - private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) { - if (mModifiedNotVcnManaged) return; - for (final int cap : nc.getCapabilities()) { - if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return; - } - nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); - } } // implement the Parcelable interface From 12aeab88bd332aff738900982a82a8c7403342a4 Mon Sep 17 00:00:00 2001 From: James Mattis Date: Sun, 10 Jan 2021 14:24:24 -0800 Subject: [PATCH 031/232] Implementation of setOemNetworkPreference Main implementation of ConnectivityService.setOemNetworkPreference. This covers the main requirements of this method including listener functionality. Bug: 176495594 Bug: 177101287 Bug: 176494815 Test: atest FrameworksNetTests atest NetworkStackTests atest FrameworksNetIntegrationTests atest NetworkStackIntegrationTests atest CtsNetTestCasesLatestSdk Change-Id: I8d318ab07785e52dd84d6261fdea8f318dce9bc5 --- .../src/android/net/ConnectivityManager.java | 60 ++++++++++++++++--- .../src/android/net/IConnectivityManager.aidl | 4 +- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 0976b753e6..c820a7e80f 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4886,15 +4886,6 @@ public class ConnectivityManager { } } - private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { - try { - mService.setOemNetworkPreference(preference); - } catch (RemoteException e) { - Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); - throw e.rethrowFromSystemServer(); - } - } - @NonNull private final List mQosCallbackConnections = new ArrayList<>(); @@ -5096,4 +5087,55 @@ public class ConnectivityManager { sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler)); } + + /** + * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor, + * OnSetOemNetworkPreferenceListener)}. + */ + private interface OnSetOemNetworkPreferenceListener { + /** + * Called when setOemNetworkPreference() successfully completes. + */ + void onComplete(); + } + + /** + * Used by automotive devices to set the network preferences used to direct traffic at an + * application level as per the given OemNetworkPreferences. An example use-case would be an + * automotive OEM wanting to provide connectivity for applications critical to the usage of a + * vehicle via a particular network. + * + * Calling this will overwrite the existing preference. + * + * @param preference {@link OemNetworkPreferences} The application network preference to be set. + * @param executor the executor on which listener will be invoked. + * @param listener {@link OnSetOemNetworkPreferenceListener} optional listener used to + * communicate completion of setOemNetworkPreference(). This will only be + * called once upon successful completion of setOemNetworkPreference(). + * @throws IllegalArgumentException if {@code preference} contains invalid preference values. + * @throws SecurityException if missing the appropriate permissions. + * @throws UnsupportedOperationException if called on a non-automotive device. + */ + private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final OnSetOemNetworkPreferenceListener listener) { + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + if (null != listener) { + Objects.requireNonNull(executor, "Executor must be non-null"); + } + final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null : + new IOnSetOemNetworkPreferenceListener.Stub() { + @Override + public void onComplete() { + executor.execute(listener::onComplete); + } + }; + + try { + mService.setOemNetworkPreference(preference, listenerInternal); + } catch (RemoteException e) { + Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); + throw e.rethrowFromSystemServer(); + } + } } diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index f909d13625..befd4fb03d 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; +import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; @@ -245,5 +246,6 @@ interface IConnectivityManager void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); void unregisterQosCallback(in IQosCallback callback); - void setOemNetworkPreference(in OemNetworkPreferences preference); + void setOemNetworkPreference(in OemNetworkPreferences preference, + in IOnSetOemNetworkPreferenceListener listener); } From a46c1446172562802d773d9b5841eec9976c6d4f Mon Sep 17 00:00:00 2001 From: James Mattis Date: Tue, 26 Jan 2021 14:05:36 -0800 Subject: [PATCH 032/232] Adding permission for OEM managed preferences Adding CONTROL_OEM_PAID_NETWORK_PREFERENCE as a signature level permission to allow an application to control OEM managed network preferences. Bug: 176496438 Bug: 176494815 Test: atest FrameworksNetTests atest NetworkStackTests atest FrameworksNetIntegrationTests atest NetworkStackIntegrationTests atest CtsNetTestCasesLatestSdk Change-Id: Iee13e89f3931c7079c2d88cb57b249b1b1cf93ad Change-Id: Id29cafe1eaf5dff8a0605cb2579204d9c77b7e70 --- framework/src/android/net/ConnectivityManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index c820a7e80f..811b053332 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5116,6 +5116,7 @@ public class ConnectivityManager { * @throws SecurityException if missing the appropriate permissions. * @throws UnsupportedOperationException if called on a non-automotive device. */ + @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, @Nullable @CallbackExecutor final Executor executor, @Nullable final OnSetOemNetworkPreferenceListener listener) { From 6e2d702d373b25603b85436e66b06315e5f12cbf Mon Sep 17 00:00:00 2001 From: James Mattis Date: Tue, 26 Jan 2021 16:23:52 -0800 Subject: [PATCH 033/232] Marking setOemNetworkPreference as @SystemApi Marking setOemNetworkPreference in ConnectivityManager as @SystemApi. Bug: 176496438 Bug: 176494815 Test: atest FrameworksNetTests atest FrameworksNetIntegrationTests atest CtsNetTestCasesLatestSdk Change-Id: I4681c88dc3a83f71c387b29610c33594e57cb43f --- framework/src/android/net/ConnectivityManager.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 811b053332..a972d9fbe9 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5091,8 +5091,10 @@ public class ConnectivityManager { /** * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor, * OnSetOemNetworkPreferenceListener)}. + * @hide */ - private interface OnSetOemNetworkPreferenceListener { + @SystemApi + public interface OnSetOemNetworkPreferenceListener { /** * Called when setOemNetworkPreference() successfully completes. */ @@ -5115,9 +5117,11 @@ public class ConnectivityManager { * @throws IllegalArgumentException if {@code preference} contains invalid preference values. * @throws SecurityException if missing the appropriate permissions. * @throws UnsupportedOperationException if called on a non-automotive device. + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) - private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, + public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, @Nullable @CallbackExecutor final Executor executor, @Nullable final OnSetOemNetworkPreferenceListener listener) { Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); From 56ad0aba41500933277fb26ec79c407be24893a9 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 12 Feb 2021 12:40:52 +0900 Subject: [PATCH 034/232] Expose VpnTransportInfo as module-lib API. This information originates in non-mainline system server code and needs to be passed to the connectivity mainline code. Bug: 173331190 Test: already covered by CTS tests Change-Id: Ic612b6a51f7ec13e2213c8754312cf716130c876 --- .../src/android/net/VpnTransportInfo.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 framework/src/android/net/VpnTransportInfo.java diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java new file mode 100644 index 0000000000..0242ba0874 --- /dev/null +++ b/framework/src/android/net/VpnTransportInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 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; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; + +import com.android.internal.util.MessageUtils; + +import java.util.Objects; + +/** + * Container for VPN-specific transport information. + * + * @see android.net.TransportInfo + * @see NetworkCapabilities#getTransportInfo() + * + * @hide + */ +@SystemApi(client = MODULE_LIBRARIES) +public final class VpnTransportInfo implements TransportInfo, Parcelable { + private static final SparseArray sTypeToString = + MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"}); + + /** Type of this VPN. */ + @VpnManager.VpnType public final int type; + + public VpnTransportInfo(@VpnManager.VpnType int type) { + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof VpnTransportInfo)) return false; + + VpnTransportInfo that = (VpnTransportInfo) o; + return this.type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + final String typeString = sTypeToString.get(type, "VPN_TYPE_???"); + return String.format("VpnTransportInfo{%s}", typeString); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(type); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + public VpnTransportInfo createFromParcel(Parcel in) { + return new VpnTransportInfo(in.readInt()); + } + public VpnTransportInfo[] newArray(int size) { + return new VpnTransportInfo[size]; + } + }; +} From b6bd8a54a11e5ce78eaca894f08b7d3c6f28dbc6 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 8 Feb 2021 16:25:42 +0900 Subject: [PATCH 035/232] Rename getVpnLockdownWhitelist to -Allowlist Test: m Change-Id: Id02a37624655c4ff88744c9c57af9f2a17953667 Merged-In: Id02a37624655c4ff88744c9c57af9f2a17953667 --- framework/src/android/net/ConnectivityManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 27386bcaa9..4213f8af95 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1117,7 +1117,7 @@ public class ConnectivityManager { * @hide */ @Deprecated - public List getVpnLockdownWhitelist(int userId) { + public List getVpnLockdownAllowlist(int userId) { return getVpnManager().getVpnLockdownAllowlist(userId); } From 2bfd3f95def6e1e3e952a38a210b44de6443426a Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 15 Feb 2021 17:07:57 +0900 Subject: [PATCH 036/232] Move the VPN code out of packages/Connectivity. Bug: 173331190 Test: atest FrameworksNetTests HostsideVpnTests CtsNetTestCases Change-Id: Idc6ed1a544e744f8661d1e387da278736d407489 --- framework/src/android/net/VpnManager.java | 406 -------- framework/src/android/net/VpnService.java | 902 ------------------ service/Android.bp | 1 - .../com_android_server_connectivity_Vpn.cpp | 377 -------- service/jni/onload.cpp | 6 +- 5 files changed, 2 insertions(+), 1690 deletions(-) delete mode 100644 framework/src/android/net/VpnManager.java delete mode 100644 framework/src/android/net/VpnService.java delete mode 100644 service/jni/com_android_server_connectivity_Vpn.cpp diff --git a/framework/src/android/net/VpnManager.java b/framework/src/android/net/VpnManager.java deleted file mode 100644 index f472ed4381..0000000000 --- a/framework/src/android/net/VpnManager.java +++ /dev/null @@ -1,406 +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; - -import static com.android.internal.util.Preconditions.checkNotNull; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.UserIdInt; -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.RemoteException; - -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; - -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.security.GeneralSecurityException; -import java.util.List; - -/** - * This class provides an interface for apps to manage platform VPN profiles - * - *

Apps can use this API to provide profiles with which the platform can set up a VPN without - * further app intermediation. When a VPN profile is present and the app is selected as an always-on - * VPN, the platform will directly trigger the negotiation of the VPN without starting or waking the - * app (unlike VpnService). - * - *

VPN apps using supported protocols should preferentially use this API over the {@link - * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user - * the guarantee that VPN network traffic is not subjected to on-device packet interception. - * - * @see Ikev2VpnProfile - */ -public class VpnManager { - /** Type representing a lack of VPN @hide */ - public static final int TYPE_VPN_NONE = -1; - - /** - * A VPN created by an app using the {@link VpnService} API. - * @hide - */ - public static final int TYPE_VPN_SERVICE = 1; - - /** - * A VPN created using a {@link VpnManager} API such as {@link #startProvisionedVpnProfile}. - * @hide - */ - public static final int TYPE_VPN_PLATFORM = 2; - - /** - * An IPsec VPN created by the built-in LegacyVpnRunner. - * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead. - * @hide - */ - @Deprecated - public static final int TYPE_VPN_LEGACY = 3; - - /** - * Channel for VPN notifications. - * @hide - */ - public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; - - /** @hide */ - @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY}) - @Retention(RetentionPolicy.SOURCE) - public @interface VpnType {} - - @NonNull private final Context mContext; - @NonNull private final IVpnManager mService; - - private static Intent getIntentForConfirmation() { - final Intent intent = new Intent(); - final ComponentName componentName = ComponentName.unflattenFromString( - Resources.getSystem().getString( - com.android.internal.R.string.config_platformVpnConfirmDialogComponent)); - intent.setComponent(componentName); - return intent; - } - - /** - * Create an instance of the VpnManager with the given context. - * - *

Internal only. Applications are expected to obtain an instance of the VpnManager via the - * {@link Context.getSystemService()} method call. - * - * @hide - */ - public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) { - mContext = checkNotNull(ctx, "missing Context"); - mService = checkNotNull(service, "missing IVpnManager"); - } - - /** - * Install a VpnProfile configuration keyed on the calling app's package name. - * - *

This method returns {@code null} if user consent has already been granted, or an {@link - * Intent} to a system activity. If an intent is returned, the application should launch the - * activity using {@link Activity#startActivityForResult} to request user consent. The activity - * may pop up a dialog to require user action, and the result will come back via its {@link - * Activity#onActivityResult}. If the result is {@link Activity#RESULT_OK}, the user has - * consented, and the VPN profile can be started. - * - * @param profile the VpnProfile provided by this package. Will override any previous VpnProfile - * stored for this package. - * @return an Intent requesting user consent to start the VPN, or null if consent is not - * required based on privileges or previous user consent. - */ - @Nullable - public Intent provisionVpnProfile(@NonNull PlatformVpnProfile profile) { - final VpnProfile internalProfile; - - try { - internalProfile = profile.toVpnProfile(); - } catch (GeneralSecurityException | IOException e) { - // Conversion to VpnProfile failed; this is an invalid profile. Both of these exceptions - // indicate a failure to convert a PrivateKey or X509Certificate to a Base64 encoded - // string as required by the VpnProfile. - throw new IllegalArgumentException("Failed to serialize PlatformVpnProfile", e); - } - - try { - // Profile can never be null; it either gets set, or an exception is thrown. - if (mService.provisionVpnProfile(internalProfile, mContext.getOpPackageName())) { - return null; - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return getIntentForConfirmation(); - } - - /** - * Delete the VPN profile configuration that was provisioned by the calling app - * - * @throws SecurityException if this would violate user settings - */ - public void deleteProvisionedVpnProfile() { - try { - mService.deleteVpnProfile(mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Request the startup of a previously provisioned VPN. - * - * @throws SecurityException exception if user or device settings prevent this VPN from being - * setup, or if user consent has not been granted - */ - public void startProvisionedVpnProfile() { - try { - mService.startVpnProfile(mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** Tear down the VPN provided by the calling app (if any) */ - public void stopProvisionedVpnProfile() { - try { - mService.stopVpnProfile(mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Return the VPN configuration for the given user ID. - * @hide - */ - @Nullable - public VpnConfig getVpnConfig(@UserIdInt int userId) { - try { - return mService.getVpnConfig(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Resets all VPN settings back to factory defaults. - * @hide - */ - @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) - public void factoryReset() { - try { - mService.factoryReset(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Prepare for a VPN application. - * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, - * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param oldPackage Package name of the application which currently controls VPN, which will - * be replaced. If there is no such application, this should should either be - * {@code null} or {@link VpnConfig.LEGACY_VPN}. - * @param newPackage Package name of the application which should gain control of VPN, or - * {@code null} to disable. - * @param userId User for whom to prepare the new VPN. - * - * @hide - */ - public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, - int userId) { - try { - return mService.prepareVpn(oldPackage, newPackage, userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Set whether the VPN package has the ability to launch VPNs without user intervention. This - * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} - * class. If the caller is not {@code userId}, {@link - * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. - * - * @param packageName The package for which authorization state should change. - * @param userId User for whom {@code packageName} is installed. - * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN - * permissions should be granted. When unauthorizing an app, {@link - * VpnManager.TYPE_VPN_NONE} should be used. - * @hide - */ - public void setVpnPackageAuthorization( - String packageName, int userId, @VpnManager.VpnType int vpnType) { - try { - mService.setVpnPackageAuthorization(packageName, userId, vpnType); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Checks if a VPN app supports always-on mode. - * - * In order to support the always-on feature, an app has to - *

    - *
  • target {@link VERSION_CODES#N API 24} or above, and - *
  • not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} - * meta-data field. - *
- * - * @param userId The identifier of the user for whom the VPN app is installed. - * @param vpnPackage The canonical package name of the VPN app. - * @return {@code true} if and only if the VPN app exists and supports always-on mode. - * @hide - */ - public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - try { - return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Configures an always-on VPN connection through a specific application. - * This connection is automatically granted and persisted after a reboot. - * - *

The designated package should declare a {@link VpnService} in its - * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, - * otherwise the call will fail. - * - * @param userId The identifier of the user to set an always-on VPN for. - * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} - * to remove an existing always-on VPN configuration. - * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. - * @param lockdownAllowlist The list of packages that are allowed to access network directly - * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so - * this method must be called when a package that should be allowed is installed or - * uninstalled. - * @return {@code true} if the package is set as always-on VPN controller; - * {@code false} otherwise. - * @hide - */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) - public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, - boolean lockdownEnabled, @Nullable List lockdownAllowlist) { - try { - return mService.setAlwaysOnVpnPackage( - userId, vpnPackage, lockdownEnabled, lockdownAllowlist); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Returns the package name of the currently set always-on VPN application. - * If there is no always-on VPN set, or the VPN is provided by the system instead - * of by an app, {@code null} will be returned. - * - * @return Package name of VPN controller responsible for always-on VPN, - * or {@code null} if none is set. - * @hide - */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) - public String getAlwaysOnVpnPackageForUser(int userId) { - try { - return mService.getAlwaysOnVpnPackage(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * @return whether always-on VPN is in lockdown mode. - * - * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) - public boolean isVpnLockdownEnabled(int userId) { - try { - return mService.isVpnLockdownEnabled(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * @return the list of packages that are allowed to access network when always-on VPN is in - * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. - * - * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) - public List getVpnLockdownAllowlist(int userId) { - try { - return mService.getVpnLockdownAllowlist(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Return the legacy VPN information for the specified user ID. - * @hide - */ - public LegacyVpnInfo getLegacyVpnInfo(@UserIdInt int userId) { - try { - return mService.getLegacyVpnInfo(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Starts a legacy VPN. - * @hide - */ - public void startLegacyVpn(VpnProfile profile) { - try { - mService.startLegacyVpn(profile); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Informs the service that legacy lockdown VPN state should be updated (e.g., if its keystore - * entry has been updated). If the LockdownVpn mechanism is enabled, updates the vpn - * with a reload of its profile. - * - *

This method can only be called by the system UID - * @return a boolean indicating success - * - * @hide - */ - public boolean updateLockdownVpn() { - try { - return mService.updateLockdownVpn(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } -} \ No newline at end of file diff --git a/framework/src/android/net/VpnService.java b/framework/src/android/net/VpnService.java deleted file mode 100644 index e43b0b6fa6..0000000000 --- a/framework/src/android/net/VpnService.java +++ /dev/null @@ -1,902 +0,0 @@ -/* - * Copyright (C) 2011 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; - -import static android.system.OsConstants.AF_INET; -import static android.system.OsConstants.AF_INET6; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SystemApi; -import android.app.Activity; -import android.app.PendingIntent; -import android.app.Service; -import android.app.admin.DevicePolicyManager; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; -import android.os.Binder; -import android.os.IBinder; -import android.os.Parcel; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserHandle; - -import com.android.internal.net.VpnConfig; - -import java.net.DatagramSocket; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.Socket; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -/** - * VpnService is a base class for applications to extend and build their - * own VPN solutions. In general, it creates a virtual network interface, - * configures addresses and routing rules, and returns a file descriptor - * to the application. Each read from the descriptor retrieves an outgoing - * packet which was routed to the interface. Each write to the descriptor - * injects an incoming packet just like it was received from the interface. - * The interface is running on Internet Protocol (IP), so packets are - * always started with IP headers. The application then completes a VPN - * connection by processing and exchanging packets with the remote server - * over a tunnel. - * - *

Letting applications intercept packets raises huge security concerns. - * A VPN application can easily break the network. Besides, two of them may - * conflict with each other. The system takes several actions to address - * these issues. Here are some key points: - *

    - *
  • User action is required the first time an application creates a VPN - * connection.
  • - *
  • There can be only one VPN connection running at the same time. The - * existing interface is deactivated when a new one is created.
  • - *
  • A system-managed notification is shown during the lifetime of a - * VPN connection.
  • - *
  • A system-managed dialog gives the information of the current VPN - * connection. It also provides a button to disconnect.
  • - *
  • The network is restored automatically when the file descriptor is - * closed. It also covers the cases when a VPN application is crashed - * or killed by the system.
  • - *
- * - *

There are two primary methods in this class: {@link #prepare} and - * {@link Builder#establish}. The former deals with user action and stops - * the VPN connection created by another application. The latter creates - * a VPN interface using the parameters supplied to the {@link Builder}. - * An application must call {@link #prepare} to grant the right to use - * other methods in this class, and the right can be revoked at any time. - * Here are the general steps to create a VPN connection: - *

    - *
  1. When the user presses the button to connect, call {@link #prepare} - * and launch the returned intent, if non-null.
  2. - *
  3. When the application becomes prepared, start the service.
  4. - *
  5. Create a tunnel to the remote server and negotiate the network - * parameters for the VPN connection.
  6. - *
  7. Supply those parameters to a {@link Builder} and create a VPN - * interface by calling {@link Builder#establish}.
  8. - *
  9. Process and exchange packets between the tunnel and the returned - * file descriptor.
  10. - *
  11. When {@link #onRevoke} is invoked, close the file descriptor and - * shut down the tunnel gracefully.
  12. - *
- * - *

Services extending this class need to be declared with an appropriate - * permission and intent filter. Their access must be secured by - * {@link android.Manifest.permission#BIND_VPN_SERVICE} permission, and - * their intent filter must match {@link #SERVICE_INTERFACE} action. Here - * is an example of declaring a VPN service in {@code AndroidManifest.xml}: - *

- * <service android:name=".ExampleVpnService"
- *         android:permission="android.permission.BIND_VPN_SERVICE">
- *     <intent-filter>
- *         <action android:name="android.net.VpnService"/>
- *     </intent-filter>
- * </service>
- * - *

The Android system starts a VPN in the background by calling - * {@link android.content.Context#startService startService()}. In Android 8.0 - * (API level 26) and higher, the system places VPN apps on the temporary - * allowlist for a short period so the app can start in the background. The VPN - * app must promote itself to the foreground after it's launched or the system - * will shut down the app. - * - *

Developer's guide

- * - *

To learn more about developing VPN apps, read the - * VPN developer's guide. - * - * @see Builder - */ -public class VpnService extends Service { - - /** - * The action must be matched by the intent filter of this service. It also - * needs to require {@link android.Manifest.permission#BIND_VPN_SERVICE} - * permission so that other applications cannot abuse it. - */ - public static final String SERVICE_INTERFACE = VpnConfig.SERVICE_INTERFACE; - - /** - * Key for boolean meta-data field indicating whether this VpnService supports always-on mode. - * - *

For a VPN app targeting {@link android.os.Build.VERSION_CODES#N API 24} or above, Android - * provides users with the ability to set it as always-on, so that VPN connection is - * persisted after device reboot and app upgrade. Always-on VPN can also be enabled by device - * owner and profile owner apps through - * {@link DevicePolicyManager#setAlwaysOnVpnPackage}. - * - *

VPN apps not supporting this feature should opt out by adding this meta-data field to the - * {@code VpnService} component of {@code AndroidManifest.xml}. In case there is more than one - * {@code VpnService} component defined in {@code AndroidManifest.xml}, opting out any one of - * them will opt out the entire app. For example, - *

 {@code
-     * 
-     *     
-     *         
-     *     
-     *     
-     * 
-     * } 
- * - *

This meta-data field defaults to {@code true} if absent. It will only have effect on - * {@link android.os.Build.VERSION_CODES#O_MR1} or higher. - */ - public static final String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = - "android.net.VpnService.SUPPORTS_ALWAYS_ON"; - - /** - * Use IVpnManager since those methods are hidden and not available in VpnManager. - */ - private static IVpnManager getService() { - return IVpnManager.Stub.asInterface( - ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE)); - } - - /** - * Prepare to establish a VPN connection. This method returns {@code null} - * if the VPN application is already prepared or if the user has previously - * consented to the VPN application. Otherwise, it returns an - * {@link Intent} to a system activity. The application should launch the - * activity using {@link Activity#startActivityForResult} to get itself - * prepared. The activity may pop up a dialog to require user action, and - * the result will come back via its {@link Activity#onActivityResult}. - * If the result is {@link Activity#RESULT_OK}, the application becomes - * prepared and is granted to use other methods in this class. - * - *

Only one application can be granted at the same time. The right - * is revoked when another application is granted. The application - * losing the right will be notified via its {@link #onRevoke}. Unless - * it becomes prepared again, subsequent calls to other methods in this - * class will fail. - * - *

The user may disable the VPN at any time while it is activated, in - * which case this method will return an intent the next time it is - * executed to obtain the user's consent again. - * - * @see #onRevoke - */ - public static Intent prepare(Context context) { - try { - if (getService().prepareVpn(context.getPackageName(), null, context.getUserId())) { - return null; - } - } catch (RemoteException e) { - // ignore - } - return VpnConfig.getIntentForConfirmation(); - } - - /** - * Version of {@link #prepare(Context)} which does not require user consent. - * - *

Requires {@link android.Manifest.permission#CONTROL_VPN} and should generally not be - * used. Only acceptable in situations where user consent has been obtained through other means. - * - *

Once this is run, future preparations may be done with the standard prepare method as this - * will authorize the package to prepare the VPN without consent in the future. - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.CONTROL_VPN) - public static void prepareAndAuthorize(Context context) { - IVpnManager vm = getService(); - String packageName = context.getPackageName(); - try { - // Only prepare if we're not already prepared. - int userId = context.getUserId(); - if (!vm.prepareVpn(packageName, null, userId)) { - vm.prepareVpn(null, packageName, userId); - } - vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); - } catch (RemoteException e) { - // ignore - } - } - - /** - * Protect a socket from VPN connections. After protecting, data sent - * through this socket will go directly to the underlying network, - * so its traffic will not be forwarded through the VPN. - * This method is useful if some connections need to be kept - * outside of VPN. For example, a VPN tunnel should protect itself if its - * destination is covered by VPN routes. Otherwise its outgoing packets - * will be sent back to the VPN interface and cause an infinite loop. This - * method will fail if the application is not prepared or is revoked. - * - *

The socket is NOT closed by this method. - * - * @return {@code true} on success. - */ - public boolean protect(int socket) { - return NetworkUtils.protectFromVpn(socket); - } - - /** - * Convenience method to protect a {@link Socket} from VPN connections. - * - * @return {@code true} on success. - * @see #protect(int) - */ - public boolean protect(Socket socket) { - return protect(socket.getFileDescriptor$().getInt$()); - } - - /** - * Convenience method to protect a {@link DatagramSocket} from VPN - * connections. - * - * @return {@code true} on success. - * @see #protect(int) - */ - public boolean protect(DatagramSocket socket) { - return protect(socket.getFileDescriptor$().getInt$()); - } - - /** - * Adds a network address to the VPN interface. - * - * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the - * address is already in use or cannot be assigned to the interface for any other reason. - * - * Adding an address implicitly allows traffic from that address family (i.e., IPv4 or IPv6) to - * be routed over the VPN. @see Builder#allowFamily - * - * @throws IllegalArgumentException if the address is invalid. - * - * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. - * @param prefixLength The prefix length of the address. - * - * @return {@code true} on success. - * @see Builder#addAddress - * - * @hide - */ - public boolean addAddress(InetAddress address, int prefixLength) { - check(address, prefixLength); - try { - return getService().addVpnAddress(address.getHostAddress(), prefixLength); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } - } - - /** - * Removes a network address from the VPN interface. - * - * Both IPv4 and IPv6 addresses are supported. The VPN must already be established. Fails if the - * address is not assigned to the VPN interface, or if it is the only address assigned (thus - * cannot be removed), or if the address cannot be removed for any other reason. - * - * After removing an address, if there are no addresses, routes or DNS servers of a particular - * address family (i.e., IPv4 or IPv6) configured on the VPN, that DOES NOT block that - * family from being routed. In other words, once an address family has been allowed, it stays - * allowed for the rest of the VPN's session. @see Builder#allowFamily - * - * @throws IllegalArgumentException if the address is invalid. - * - * @param address The IP address (IPv4 or IPv6) to assign to the VPN interface. - * @param prefixLength The prefix length of the address. - * - * @return {@code true} on success. - * - * @hide - */ - public boolean removeAddress(InetAddress address, int prefixLength) { - check(address, prefixLength); - try { - return getService().removeVpnAddress(address.getHostAddress(), prefixLength); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } - } - - /** - * Sets the underlying networks used by the VPN for its upstream connections. - * - *

Used by the system to know the actual networks that carry traffic for apps affected by - * this VPN in order to present this information to the user (e.g., via status bar icons). - * - *

This method only needs to be called if the VPN has explicitly bound its underlying - * communications channels — such as the socket(s) passed to {@link #protect(int)} — - * to a {@code Network} using APIs such as {@link Network#bindSocket(Socket)} or - * {@link Network#bindSocket(DatagramSocket)}. The VPN should call this method every time - * the set of {@code Network}s it is using changes. - * - *

{@code networks} is one of the following: - *

    - *
  • a non-empty array: an array of one or more {@link Network}s, in - * decreasing preference order. For example, if this VPN uses both wifi and mobile (cellular) - * networks to carry app traffic, but prefers or uses wifi more than mobile, wifi should appear - * first in the array.
  • - *
  • an empty array: a zero-element array, meaning that the VPN has no - * underlying network connection, and thus, app traffic will not be sent or received.
  • - *
  • null: (default) signifies that the VPN uses whatever is the system's - * default network. I.e., it doesn't use the {@code bindSocket} or {@code bindDatagramSocket} - * APIs mentioned above to send traffic over specific channels.
  • - *
- * - *

This call will succeed only if the VPN is currently established. For setting this value - * when the VPN has not yet been established, see {@link Builder#setUnderlyingNetworks}. - * - * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. - * - * @return {@code true} on success. - */ - public boolean setUnderlyingNetworks(Network[] networks) { - try { - return getService().setUnderlyingNetworksForVpn(networks); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } - } - - /** - * Returns whether the service is running in always-on VPN mode. In this mode the system ensures - * that the service is always running by restarting it when necessary, e.g. after reboot. - * - * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set) - */ - public final boolean isAlwaysOn() { - try { - return getService().isCallerCurrentAlwaysOnVpnApp(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Returns whether the service is running in always-on VPN lockdown mode. In this mode the - * system ensures that the service is always running and that the apps aren't allowed to bypass - * the VPN. - * - * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set) - */ - public final boolean isLockdownEnabled() { - try { - return getService().isCallerCurrentAlwaysOnVpnLockdownApp(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Return the communication interface to the service. This method returns - * {@code null} on {@link Intent}s other than {@link #SERVICE_INTERFACE} - * action. Applications overriding this method must identify the intent - * and return the corresponding interface accordingly. - * - * @see Service#onBind - */ - @Override - public IBinder onBind(Intent intent) { - if (intent != null && SERVICE_INTERFACE.equals(intent.getAction())) { - return new Callback(); - } - return null; - } - - /** - * Invoked when the application is revoked. At this moment, the VPN - * interface is already deactivated by the system. The application should - * close the file descriptor and shut down gracefully. The default - * implementation of this method is calling {@link Service#stopSelf()}. - * - *

Calls to this method may not happen on the main thread - * of the process. - * - * @see #prepare - */ - public void onRevoke() { - stopSelf(); - } - - /** - * Use raw Binder instead of AIDL since now there is only one usage. - */ - private class Callback extends Binder { - @Override - protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { - if (code == IBinder.LAST_CALL_TRANSACTION) { - onRevoke(); - return true; - } - return false; - } - } - - /** - * Private method to validate address and prefixLength. - */ - private static void check(InetAddress address, int prefixLength) { - if (address.isLoopbackAddress()) { - throw new IllegalArgumentException("Bad address"); - } - if (address instanceof Inet4Address) { - if (prefixLength < 0 || prefixLength > 32) { - throw new IllegalArgumentException("Bad prefixLength"); - } - } else if (address instanceof Inet6Address) { - if (prefixLength < 0 || prefixLength > 128) { - throw new IllegalArgumentException("Bad prefixLength"); - } - } else { - throw new IllegalArgumentException("Unsupported family"); - } - } - - /** - * Helper class to create a VPN interface. This class should be always - * used within the scope of the outer {@link VpnService}. - * - * @see VpnService - */ - public class Builder { - - private final VpnConfig mConfig = new VpnConfig(); - @UnsupportedAppUsage - private final List mAddresses = new ArrayList(); - @UnsupportedAppUsage - private final List mRoutes = new ArrayList(); - - public Builder() { - mConfig.user = VpnService.this.getClass().getName(); - } - - /** - * Set the name of this session. It will be displayed in - * system-managed dialogs and notifications. This is recommended - * not required. - */ - @NonNull - public Builder setSession(@NonNull String session) { - mConfig.session = session; - return this; - } - - /** - * Set the {@link PendingIntent} to an activity for users to - * configure the VPN connection. If it is not set, the button - * to configure will not be shown in system-managed dialogs. - */ - @NonNull - public Builder setConfigureIntent(@NonNull PendingIntent intent) { - mConfig.configureIntent = intent; - return this; - } - - /** - * Set the maximum transmission unit (MTU) of the VPN interface. If - * it is not set, the default value in the operating system will be - * used. - * - * @throws IllegalArgumentException if the value is not positive. - */ - @NonNull - public Builder setMtu(int mtu) { - if (mtu <= 0) { - throw new IllegalArgumentException("Bad mtu"); - } - mConfig.mtu = mtu; - return this; - } - - /** - * Sets an HTTP proxy for the VPN network. This proxy is only a recommendation - * and it is possible that some apps will ignore it. - */ - @NonNull - public Builder setHttpProxy(@NonNull ProxyInfo proxyInfo) { - mConfig.proxyInfo = proxyInfo; - return this; - } - - /** - * Add a network address to the VPN interface. Both IPv4 and IPv6 - * addresses are supported. At least one address must be set before - * calling {@link #establish}. - * - * Adding an address implicitly allows traffic from that address family - * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily - * - * @throws IllegalArgumentException if the address is invalid. - */ - @NonNull - public Builder addAddress(@NonNull InetAddress address, int prefixLength) { - check(address, prefixLength); - - if (address.isAnyLocalAddress()) { - throw new IllegalArgumentException("Bad address"); - } - mAddresses.add(new LinkAddress(address, prefixLength)); - mConfig.updateAllowedFamilies(address); - return this; - } - - /** - * Convenience method to add a network address to the VPN interface - * using a numeric address string. See {@link InetAddress} for the - * definitions of numeric address formats. - * - * Adding an address implicitly allows traffic from that address family - * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily - * - * @throws IllegalArgumentException if the address is invalid. - * @see #addAddress(InetAddress, int) - */ - @NonNull - public Builder addAddress(@NonNull String address, int prefixLength) { - return addAddress(InetAddress.parseNumericAddress(address), prefixLength); - } - - /** - * Add a network route to the VPN interface. Both IPv4 and IPv6 - * routes are supported. - * - * Adding a route implicitly allows traffic from that address family - * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily - * - * @throws IllegalArgumentException if the route is invalid. - */ - @NonNull - public Builder addRoute(@NonNull InetAddress address, int prefixLength) { - check(address, prefixLength); - - int offset = prefixLength / 8; - byte[] bytes = address.getAddress(); - if (offset < bytes.length) { - for (bytes[offset] <<= prefixLength % 8; offset < bytes.length; ++offset) { - if (bytes[offset] != 0) { - throw new IllegalArgumentException("Bad address"); - } - } - } - mRoutes.add(new RouteInfo(new IpPrefix(address, prefixLength), null)); - mConfig.updateAllowedFamilies(address); - return this; - } - - /** - * Convenience method to add a network route to the VPN interface - * using a numeric address string. See {@link InetAddress} for the - * definitions of numeric address formats. - * - * Adding a route implicitly allows traffic from that address family - * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily - * - * @throws IllegalArgumentException if the route is invalid. - * @see #addRoute(InetAddress, int) - */ - @NonNull - public Builder addRoute(@NonNull String address, int prefixLength) { - return addRoute(InetAddress.parseNumericAddress(address), prefixLength); - } - - /** - * Add a DNS server to the VPN connection. Both IPv4 and IPv6 - * addresses are supported. If none is set, the DNS servers of - * the default network will be used. - * - * Adding a server implicitly allows traffic from that address family - * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily - * - * @throws IllegalArgumentException if the address is invalid. - */ - @NonNull - public Builder addDnsServer(@NonNull InetAddress address) { - if (address.isLoopbackAddress() || address.isAnyLocalAddress()) { - throw new IllegalArgumentException("Bad address"); - } - if (mConfig.dnsServers == null) { - mConfig.dnsServers = new ArrayList(); - } - mConfig.dnsServers.add(address.getHostAddress()); - return this; - } - - /** - * Convenience method to add a DNS server to the VPN connection - * using a numeric address string. See {@link InetAddress} for the - * definitions of numeric address formats. - * - * Adding a server implicitly allows traffic from that address family - * (i.e., IPv4 or IPv6) to be routed over the VPN. @see #allowFamily - * - * @throws IllegalArgumentException if the address is invalid. - * @see #addDnsServer(InetAddress) - */ - @NonNull - public Builder addDnsServer(@NonNull String address) { - return addDnsServer(InetAddress.parseNumericAddress(address)); - } - - /** - * Add a search domain to the DNS resolver. - */ - @NonNull - public Builder addSearchDomain(@NonNull String domain) { - if (mConfig.searchDomains == null) { - mConfig.searchDomains = new ArrayList(); - } - mConfig.searchDomains.add(domain); - return this; - } - - /** - * Allows traffic from the specified address family. - * - * By default, if no address, route or DNS server of a specific family (IPv4 or IPv6) is - * added to this VPN, then all outgoing traffic of that family is blocked. If any address, - * route or DNS server is added, that family is allowed. - * - * This method allows an address family to be unblocked even without adding an address, - * route or DNS server of that family. Traffic of that family will then typically - * fall-through to the underlying network if it's supported. - * - * {@code family} must be either {@code AF_INET} (for IPv4) or {@code AF_INET6} (for IPv6). - * {@link IllegalArgumentException} is thrown if it's neither. - * - * @param family The address family ({@code AF_INET} or {@code AF_INET6}) to allow. - * - * @return this {@link Builder} object to facilitate chaining of method calls. - */ - @NonNull - public Builder allowFamily(int family) { - if (family == AF_INET) { - mConfig.allowIPv4 = true; - } else if (family == AF_INET6) { - mConfig.allowIPv6 = true; - } else { - throw new IllegalArgumentException(family + " is neither " + AF_INET + " nor " + - AF_INET6); - } - return this; - } - - private void verifyApp(String packageName) throws PackageManager.NameNotFoundException { - IPackageManager pm = IPackageManager.Stub.asInterface( - ServiceManager.getService("package")); - try { - pm.getApplicationInfo(packageName, 0, UserHandle.getCallingUserId()); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } - } - - /** - * Adds an application that's allowed to access the VPN connection. - * - * If this method is called at least once, only applications added through this method (and - * no others) are allowed access. Else (if this method is never called), all applications - * are allowed by default. If some applications are added, other, un-added applications - * will use networking as if the VPN wasn't running. - * - * A {@link Builder} may have only a set of allowed applications OR a set of disallowed - * ones, but not both. Calling this method after {@link #addDisallowedApplication} has - * already been called, or vice versa, will throw an {@link UnsupportedOperationException}. - * - * {@code packageName} must be the canonical name of a currently installed application. - * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. - * - * @throws PackageManager.NameNotFoundException If the application isn't installed. - * - * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. - * - * @return this {@link Builder} object to facilitate chaining method calls. - */ - @NonNull - public Builder addAllowedApplication(@NonNull String packageName) - throws PackageManager.NameNotFoundException { - if (mConfig.disallowedApplications != null) { - throw new UnsupportedOperationException("addDisallowedApplication already called"); - } - verifyApp(packageName); - if (mConfig.allowedApplications == null) { - mConfig.allowedApplications = new ArrayList(); - } - mConfig.allowedApplications.add(packageName); - return this; - } - - /** - * Adds an application that's denied access to the VPN connection. - * - * By default, all applications are allowed access, except for those denied through this - * method. Denied applications will use networking as if the VPN wasn't running. - * - * A {@link Builder} may have only a set of allowed applications OR a set of disallowed - * ones, but not both. Calling this method after {@link #addAllowedApplication} has already - * been called, or vice versa, will throw an {@link UnsupportedOperationException}. - * - * {@code packageName} must be the canonical name of a currently installed application. - * {@link PackageManager.NameNotFoundException} is thrown if there's no such application. - * - * @throws PackageManager.NameNotFoundException If the application isn't installed. - * - * @param packageName The full name (e.g.: "com.google.apps.contacts") of an application. - * - * @return this {@link Builder} object to facilitate chaining method calls. - */ - @NonNull - public Builder addDisallowedApplication(@NonNull String packageName) - throws PackageManager.NameNotFoundException { - if (mConfig.allowedApplications != null) { - throw new UnsupportedOperationException("addAllowedApplication already called"); - } - verifyApp(packageName); - if (mConfig.disallowedApplications == null) { - mConfig.disallowedApplications = new ArrayList(); - } - mConfig.disallowedApplications.add(packageName); - return this; - } - - /** - * Allows all apps to bypass this VPN connection. - * - * By default, all traffic from apps is forwarded through the VPN interface and it is not - * possible for apps to side-step the VPN. If this method is called, apps may use methods - * such as {@link ConnectivityManager#bindProcessToNetwork} to instead send/receive - * directly over the underlying network or any other network they have permissions for. - * - * @return this {@link Builder} object to facilitate chaining of method calls. - */ - @NonNull - public Builder allowBypass() { - mConfig.allowBypass = true; - return this; - } - - /** - * Sets the VPN interface's file descriptor to be in blocking/non-blocking mode. - * - * By default, the file descriptor returned by {@link #establish} is non-blocking. - * - * @param blocking True to put the descriptor into blocking mode; false for non-blocking. - * - * @return this {@link Builder} object to facilitate chaining method calls. - */ - @NonNull - public Builder setBlocking(boolean blocking) { - mConfig.blocking = blocking; - return this; - } - - /** - * Sets the underlying networks used by the VPN for its upstream connections. - * - * @see VpnService#setUnderlyingNetworks - * - * @param networks An array of networks the VPN uses to tunnel traffic to/from its servers. - * - * @return this {@link Builder} object to facilitate chaining method calls. - */ - @NonNull - public Builder setUnderlyingNetworks(@Nullable Network[] networks) { - mConfig.underlyingNetworks = networks != null ? networks.clone() : null; - return this; - } - - /** - * Marks the VPN network as metered. A VPN network is classified as metered when the user is - * sensitive to heavy data usage due to monetary costs and/or data limitations. In such - * cases, you should set this to {@code true} so that apps on the system can avoid doing - * large data transfers. Otherwise, set this to {@code false}. Doing so would cause VPN - * network to inherit its meteredness from its underlying networks. - * - *

VPN apps targeting {@link android.os.Build.VERSION_CODES#Q} or above will be - * considered metered by default. - * - * @param isMetered {@code true} if VPN network should be treated as metered regardless of - * underlying network meteredness - * @return this {@link Builder} object to facilitate chaining method calls - * @see #setUnderlyingNetworks(Network[]) - * @see ConnectivityManager#isActiveNetworkMetered() - */ - @NonNull - public Builder setMetered(boolean isMetered) { - mConfig.isMetered = isMetered; - return this; - } - - /** - * Create a VPN interface using the parameters supplied to this - * builder. The interface works on IP packets, and a file descriptor - * is returned for the application to access them. Each read - * retrieves an outgoing packet which was routed to the interface. - * Each write injects an incoming packet just like it was received - * from the interface. The file descriptor is put into non-blocking - * mode by default to avoid blocking Java threads. To use the file - * descriptor completely in native space, see - * {@link ParcelFileDescriptor#detachFd()}. The application MUST - * close the file descriptor when the VPN connection is terminated. - * The VPN interface will be removed and the network will be - * restored by the system automatically. - * - *

To avoid conflicts, there can be only one active VPN interface - * at the same time. Usually network parameters are never changed - * during the lifetime of a VPN connection. It is also common for an - * application to create a new file descriptor after closing the - * previous one. However, it is rare but not impossible to have two - * interfaces while performing a seamless handover. In this case, the - * old interface will be deactivated when the new one is created - * successfully. Both file descriptors are valid but now outgoing - * packets will be routed to the new interface. Therefore, after - * draining the old file descriptor, the application MUST close it - * and start using the new file descriptor. If the new interface - * cannot be created, the existing interface and its file descriptor - * remain untouched. - * - *

An exception will be thrown if the interface cannot be created - * for any reason. However, this method returns {@code null} if the - * application is not prepared or is revoked. This helps solve - * possible race conditions between other VPN applications. - * - * @return {@link ParcelFileDescriptor} of the VPN interface, or - * {@code null} if the application is not prepared. - * @throws IllegalArgumentException if a parameter is not accepted - * by the operating system. - * @throws IllegalStateException if a parameter cannot be applied - * by the operating system. - * @throws SecurityException if the service is not properly declared - * in {@code AndroidManifest.xml}. - * @see VpnService - */ - @Nullable - public ParcelFileDescriptor establish() { - mConfig.addresses = mAddresses; - mConfig.routes = mRoutes; - - try { - return getService().establishVpn(mConfig); - } catch (RemoteException e) { - throw new IllegalStateException(e); - } - } - } -} diff --git a/service/Android.bp b/service/Android.bp index 8fc3181807..ed1716fad8 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -25,7 +25,6 @@ cc_library_shared { ], srcs: [ "jni/com_android_server_TestNetworkService.cpp", - "jni/com_android_server_connectivity_Vpn.cpp", "jni/onload.cpp", ], shared_libs: [ diff --git a/service/jni/com_android_server_connectivity_Vpn.cpp b/service/jni/com_android_server_connectivity_Vpn.cpp deleted file mode 100644 index ea5e7183c9..0000000000 --- a/service/jni/com_android_server_connectivity_Vpn.cpp +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_NDEBUG 0 - -#define LOG_TAG "VpnJni" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "netutils/ifc.h" - -#include "jni.h" -#include - -namespace android -{ - -static int inet4 = -1; -static int inet6 = -1; - -static inline in_addr_t *as_in_addr(sockaddr *sa) { - return &((sockaddr_in *)sa)->sin_addr.s_addr; -} - -//------------------------------------------------------------------------------ - -#define SYSTEM_ERROR (-1) -#define BAD_ARGUMENT (-2) - -static int create_interface(int mtu) -{ - int tun = open("/dev/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC); - - ifreq ifr4; - memset(&ifr4, 0, sizeof(ifr4)); - - // Allocate interface. - ifr4.ifr_flags = IFF_TUN | IFF_NO_PI; - if (ioctl(tun, TUNSETIFF, &ifr4)) { - ALOGE("Cannot allocate TUN: %s", strerror(errno)); - goto error; - } - - // Activate interface. - ifr4.ifr_flags = IFF_UP; - if (ioctl(inet4, SIOCSIFFLAGS, &ifr4)) { - ALOGE("Cannot activate %s: %s", ifr4.ifr_name, strerror(errno)); - goto error; - } - - // Set MTU if it is specified. - ifr4.ifr_mtu = mtu; - if (mtu > 0 && ioctl(inet4, SIOCSIFMTU, &ifr4)) { - ALOGE("Cannot set MTU on %s: %s", ifr4.ifr_name, strerror(errno)); - goto error; - } - - return tun; - -error: - close(tun); - return SYSTEM_ERROR; -} - -static int get_interface_name(char *name, int tun) -{ - ifreq ifr4; - if (ioctl(tun, TUNGETIFF, &ifr4)) { - ALOGE("Cannot get interface name: %s", strerror(errno)); - return SYSTEM_ERROR; - } - strncpy(name, ifr4.ifr_name, IFNAMSIZ); - return 0; -} - -static int get_interface_index(const char *name) -{ - ifreq ifr4; - strncpy(ifr4.ifr_name, name, IFNAMSIZ); - if (ioctl(inet4, SIOGIFINDEX, &ifr4)) { - ALOGE("Cannot get index of %s: %s", name, strerror(errno)); - return SYSTEM_ERROR; - } - return ifr4.ifr_ifindex; -} - -static int set_addresses(const char *name, const char *addresses) -{ - int index = get_interface_index(name); - if (index < 0) { - return index; - } - - ifreq ifr4; - memset(&ifr4, 0, sizeof(ifr4)); - strncpy(ifr4.ifr_name, name, IFNAMSIZ); - ifr4.ifr_addr.sa_family = AF_INET; - ifr4.ifr_netmask.sa_family = AF_INET; - - in6_ifreq ifr6; - memset(&ifr6, 0, sizeof(ifr6)); - ifr6.ifr6_ifindex = index; - - char address[65]; - int prefix; - int chars; - int count = 0; - - while (sscanf(addresses, " %64[^/]/%d %n", address, &prefix, &chars) == 2) { - addresses += chars; - - if (strchr(address, ':')) { - // Add an IPv6 address. - if (inet_pton(AF_INET6, address, &ifr6.ifr6_addr) != 1 || - prefix < 0 || prefix > 128) { - count = BAD_ARGUMENT; - break; - } - - ifr6.ifr6_prefixlen = prefix; - if (ioctl(inet6, SIOCSIFADDR, &ifr6)) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - } else { - // Add an IPv4 address. - if (inet_pton(AF_INET, address, as_in_addr(&ifr4.ifr_addr)) != 1 || - prefix < 0 || prefix > 32) { - count = BAD_ARGUMENT; - break; - } - - if (count) { - snprintf(ifr4.ifr_name, sizeof(ifr4.ifr_name), "%s:%d", name, count); - } - if (ioctl(inet4, SIOCSIFADDR, &ifr4)) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - - in_addr_t mask = prefix ? (~0 << (32 - prefix)) : 0; - *as_in_addr(&ifr4.ifr_netmask) = htonl(mask); - if (ioctl(inet4, SIOCSIFNETMASK, &ifr4)) { - count = (errno == EINVAL) ? BAD_ARGUMENT : SYSTEM_ERROR; - break; - } - } - ALOGD("Address added on %s: %s/%d", name, address, prefix); - ++count; - } - - if (count == BAD_ARGUMENT) { - ALOGE("Invalid address: %s/%d", address, prefix); - } else if (count == SYSTEM_ERROR) { - ALOGE("Cannot add address: %s/%d: %s", address, prefix, strerror(errno)); - } else if (*addresses) { - ALOGE("Invalid address: %s", addresses); - count = BAD_ARGUMENT; - } - - return count; -} - -static int reset_interface(const char *name) -{ - ifreq ifr4; - strncpy(ifr4.ifr_name, name, IFNAMSIZ); - ifr4.ifr_flags = 0; - - if (ioctl(inet4, SIOCSIFFLAGS, &ifr4) && errno != ENODEV) { - ALOGE("Cannot reset %s: %s", name, strerror(errno)); - return SYSTEM_ERROR; - } - return 0; -} - -static int check_interface(const char *name) -{ - ifreq ifr4; - strncpy(ifr4.ifr_name, name, IFNAMSIZ); - ifr4.ifr_flags = 0; - - if (ioctl(inet4, SIOCGIFFLAGS, &ifr4) && errno != ENODEV) { - ALOGE("Cannot check %s: %s", name, strerror(errno)); - } - return ifr4.ifr_flags; -} - -static bool modifyAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, - jint jPrefixLength, bool add) -{ - int error = SYSTEM_ERROR; - const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; - const char *address = jAddress ? env->GetStringUTFChars(jAddress, NULL) : NULL; - - if (!name) { - jniThrowNullPointerException(env, "name"); - } else if (!address) { - jniThrowNullPointerException(env, "address"); - } else { - if (add) { - if ((error = ifc_add_address(name, address, jPrefixLength)) != 0) { - ALOGE("Cannot add address %s/%d on interface %s (%s)", address, jPrefixLength, name, - strerror(-error)); - } - } else { - if ((error = ifc_del_address(name, address, jPrefixLength)) != 0) { - ALOGE("Cannot del address %s/%d on interface %s (%s)", address, jPrefixLength, name, - strerror(-error)); - } - } - } - - if (name) { - env->ReleaseStringUTFChars(jName, name); - } - if (address) { - env->ReleaseStringUTFChars(jAddress, address); - } - return !error; -} - -//------------------------------------------------------------------------------ - -static void throwException(JNIEnv *env, int error, const char *message) -{ - if (error == SYSTEM_ERROR) { - jniThrowException(env, "java/lang/IllegalStateException", message); - } else { - jniThrowException(env, "java/lang/IllegalArgumentException", message); - } -} - -static jint create(JNIEnv *env, jobject /* thiz */, jint mtu) -{ - int tun = create_interface(mtu); - if (tun < 0) { - throwException(env, tun, "Cannot create interface"); - return -1; - } - return tun; -} - -static jstring getName(JNIEnv *env, jobject /* thiz */, jint tun) -{ - char name[IFNAMSIZ]; - if (get_interface_name(name, tun) < 0) { - throwException(env, SYSTEM_ERROR, "Cannot get interface name"); - return NULL; - } - return env->NewStringUTF(name); -} - -static jint setAddresses(JNIEnv *env, jobject /* thiz */, jstring jName, - jstring jAddresses) -{ - const char *name = NULL; - const char *addresses = NULL; - int count = -1; - - name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; - if (!name) { - jniThrowNullPointerException(env, "name"); - goto error; - } - addresses = jAddresses ? env->GetStringUTFChars(jAddresses, NULL) : NULL; - if (!addresses) { - jniThrowNullPointerException(env, "addresses"); - goto error; - } - count = set_addresses(name, addresses); - if (count < 0) { - throwException(env, count, "Cannot set address"); - count = -1; - } - -error: - if (name) { - env->ReleaseStringUTFChars(jName, name); - } - if (addresses) { - env->ReleaseStringUTFChars(jAddresses, addresses); - } - return count; -} - -static void reset(JNIEnv *env, jobject /* thiz */, jstring jName) -{ - const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; - if (!name) { - jniThrowNullPointerException(env, "name"); - return; - } - if (reset_interface(name) < 0) { - throwException(env, SYSTEM_ERROR, "Cannot reset interface"); - } - env->ReleaseStringUTFChars(jName, name); -} - -static jint check(JNIEnv *env, jobject /* thiz */, jstring jName) -{ - const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; - if (!name) { - jniThrowNullPointerException(env, "name"); - return 0; - } - int flags = check_interface(name); - env->ReleaseStringUTFChars(jName, name); - return flags; -} - -static bool addAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, - jint jPrefixLength) -{ - return modifyAddress(env, thiz, jName, jAddress, jPrefixLength, true); -} - -static bool delAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAddress, - jint jPrefixLength) -{ - return modifyAddress(env, thiz, jName, jAddress, jPrefixLength, false); -} - -//------------------------------------------------------------------------------ - -static const JNINativeMethod gMethods[] = { - {"jniCreate", "(I)I", (void *)create}, - {"jniGetName", "(I)Ljava/lang/String;", (void *)getName}, - {"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses}, - {"jniReset", "(Ljava/lang/String;)V", (void *)reset}, - {"jniCheck", "(Ljava/lang/String;)I", (void *)check}, - {"jniAddAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)addAddress}, - {"jniDelAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)delAddress}, -}; - -int register_android_server_connectivity_Vpn(JNIEnv *env) -{ - if (inet4 == -1) { - inet4 = socket(AF_INET, SOCK_DGRAM, 0); - } - if (inet6 == -1) { - inet6 = socket(AF_INET6, SOCK_DGRAM, 0); - } - return jniRegisterNativeMethods(env, "com/android/server/connectivity/Vpn", - gMethods, NELEM(gMethods)); -} - -}; diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp index 3afcb0e8f6..00128794bc 100644 --- a/service/jni/onload.cpp +++ b/service/jni/onload.cpp @@ -19,7 +19,6 @@ namespace android { -int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_TestNetworkService(JNIEnv* env); extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { @@ -29,12 +28,11 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { return JNI_ERR; } - if (register_android_server_connectivity_Vpn(env) < 0 - || register_android_server_TestNetworkService(env) < 0) { + if (register_android_server_TestNetworkService(env) < 0) { return JNI_ERR; } return JNI_VERSION_1_6; } -}; \ No newline at end of file +}; From c4e2f637584a4a1928cebe1b3efc0c4a613fc1c0 Mon Sep 17 00:00:00 2001 From: lifr Date: Mon, 1 Feb 2021 16:52:18 +0800 Subject: [PATCH 037/232] [CS01]Remove hidden API usage of NetworkCapabilities The connection service will become the mainline module. The mutable NetworkCapabilities is deprecated, and the NetworkCapabilities should be built through their Builder instead. Bug: 170598012 Test: atest FrameworksNetTests Test: atest IpConnectivityMetricsTest Change-Id: I73a4d3a7c118b9cef037ed52efb96ed123da2fa5 --- framework/src/android/net/NetworkCapabilities.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 9d67f0b843..26d14cbfaa 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -2085,9 +2085,10 @@ public final class NetworkCapabilities implements Parcelable { /** * Check if private dns is broken. * - * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken. + * @return {@code true} if private DNS is broken on this network. * @hide */ + @SystemApi public boolean isPrivateDnsBroken() { return mPrivateDnsBroken; } @@ -2329,6 +2330,17 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Completely clears the contents of this object, removing even the capabilities that are + * set by default when the object is constructed. + * @return this builder + */ + @NonNull + public Builder clearAll() { + mCaps.clearAll(); + return this; + } + /** * Sets the owner UID. * From 6c7ed9dddc93ff7aea2c2508058b3a441a335d70 Mon Sep 17 00:00:00 2001 From: Bob Badour Date: Fri, 12 Feb 2021 17:07:05 -0800 Subject: [PATCH 038/232] [LSC] Add LOCAL_LICENSE_KINDS to frameworks/base Added SPDX-license-identifier-Apache-2.0 to: apct-tests/perftests/autofill/Android.bp apct-tests/perftests/blobstore/Android.bp apct-tests/perftests/core/Android.bp apct-tests/perftests/core/apps/overlay/Android.bp apct-tests/perftests/core/apps/reources_manager/Android.bp apct-tests/perftests/core/jni/Android.bp apct-tests/perftests/multiuser/Android.bp apct-tests/perftests/multiuser/apps/dummyapp/Android.bp apct-tests/perftests/packagemanager/Android.bp apct-tests/perftests/packagemanager/apps/query-all/Android.bp apct-tests/perftests/textclassifier/Android.bp apct-tests/perftests/utils/Android.bp apct-tests/perftests/windowmanager/Android.bp apex/Android.bp apex/blobstore/framework/Android.bp apex/blobstore/service/Android.bp apex/jobscheduler/framework/Android.bp apex/jobscheduler/service/Android.bp apex/media/Android.bp apex/media/aidl/Android.bp apex/media/framework/Android.bp cmds/am/Android.bp cmds/app_process/Android.bp cmds/appops/Android.bp cmds/appwidget/Android.bp cmds/backup/Android.bp cmds/bmgr/Android.bp cmds/bootanimation/Android.bp cmds/bu/Android.bp cmds/content/Android.bp cmds/dpm/Android.bp cmds/hid/Android.bp cmds/hid/jni/Android.bp cmds/idmap2/Android.bp cmds/ime/Android.bp cmds/incident/Android.bp cmds/incident_helper/Android.bp cmds/incidentd/Android.bp cmds/input/Android.bp cmds/interrupter/Android.bp cmds/locksettings/Android.bp cmds/pm/Android.bp cmds/requestsync/Android.bp cmds/screencap/Android.bp cmds/sm/Android.bp cmds/svc/Android.bp cmds/telecom/Android.bp cmds/uiautomator/Android.bp cmds/uiautomator/cmds/uiautomator/Android.bp cmds/uiautomator/instrumentation/Android.bp cmds/uiautomator/library/Android.bp cmds/vr/Android.bp cmds/wm/Android.bp config/Android.bp core/java/android/service/wallpaper/Android.bp core/jni/Android.bp core/sysprop/Android.bp core/tests/BroadcastRadioTests/Android.bp core/tests/ConnectivityManagerTest/Android.bp core/tests/PackageInstallerSessions/Android.bp core/tests/PlatformCompatFramework/Android.bp core/tests/bandwidthtests/Android.bp core/tests/benchmarks/Android.bp core/tests/bluetoothtests/Android.bp core/tests/bugreports/Android.bp core/tests/coretests/Android.bp core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp core/tests/coretests/BinderProxyCountingTestApp/Android.bp core/tests/coretests/BinderProxyCountingTestService/Android.bp core/tests/coretests/BstatsTestApp/Android.bp core/tests/coretests/DisabledTestApp/Android.bp core/tests/coretests/EnabledTestApp/Android.bp core/tests/coretests/aidl/Android.bp core/tests/coretests/apks/Android.bp core/tests/coretests/apks/install/Android.bp core/tests/coretests/apks/install_bad_dex/Android.bp core/tests/coretests/apks/install_complete_package_info/Android.bp core/tests/coretests/apks/install_decl_perm/Android.bp core/tests/coretests/apks/install_jni_lib/Android.bp core/tests/coretests/apks/install_jni_lib_open_from_apk/Android.bp core/tests/coretests/apks/install_loc_auto/Android.bp core/tests/coretests/apks/install_loc_internal/Android.bp core/tests/coretests/apks/install_loc_sdcard/Android.bp core/tests/coretests/apks/install_loc_unspecified/Android.bp core/tests/coretests/apks/install_use_perm_good/Android.bp core/tests/coretests/apks/install_uses_feature/Android.bp core/tests/coretests/apks/install_verifier_bad/Android.bp core/tests/coretests/apks/install_verifier_good/Android.bp core/tests/coretests/apks/keyset/Android.bp core/tests/coretests/apks/locales/Android.bp core/tests/coretests/apks/overlay_config/Android.bp core/tests/coretests/apks/version/Android.bp core/tests/coretests/apks/version_nosys/Android.bp core/tests/featureflagtests/Android.bp core/tests/hdmitests/Android.bp core/tests/hosttests/test-apps/AutoLocTestApp/Android.bp core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/Android.bp core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/Android.bp core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.bp core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/Android.bp core/tests/hosttests/test-apps/ExternalLocTestApp/Android.bp core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/Android.bp core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/Android.bp core/tests/hosttests/test-apps/ExternalSharedPerms/Android.bp core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.bp core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.bp core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.bp core/tests/hosttests/test-apps/InternalLocTestApp/Android.bp core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests/Android.bp core/tests/hosttests/test-apps/NoLocTestApp/Android.bp core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/Android.bp core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/Android.bp core/tests/hosttests/test-apps/SharedUid/32/Android.bp core/tests/hosttests/test-apps/SharedUid/32/jni/Android.bp core/tests/hosttests/test-apps/SharedUid/64/Android.bp core/tests/hosttests/test-apps/SharedUid/64/jni/Android.bp core/tests/hosttests/test-apps/SharedUid/dual/Android.bp core/tests/hosttests/test-apps/SharedUid/dual/jni/Android.bp core/tests/hosttests/test-apps/SharedUid/java_only/Android.bp core/tests/hosttests/test-apps/SimpleTestApp/Android.bp core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/Android.bp core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/Android.bp core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/Android.bp core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/Android.bp core/tests/hosttests/test-apps/VersatileTestApp_Auto/Android.bp core/tests/hosttests/test-apps/VersatileTestApp_External/Android.bp core/tests/hosttests/test-apps/VersatileTestApp_Internal/Android.bp core/tests/hosttests/test-apps/VersatileTestApp_None/Android.bp core/tests/mockingcoretests/Android.bp core/tests/notificationtests/Android.bp core/tests/overlaytests/device/Android.bp core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp core/tests/overlaytests/device/test-apps/FrameworkOverlay/Android.bp core/tests/overlaytests/host/Android.bp core/tests/overlaytests/remount/Android.bp core/tests/overlaytests/remount/test-apps/Overlay/Android.bp core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp core/tests/overlaytests/remount/test-apps/Target/Android.bp core/tests/packagemanagertests/Android.bp core/tests/privacytests/Android.bp core/tests/screenshothelpertests/Android.bp core/tests/systemproperties/Android.bp core/tests/utillib/Android.bp core/tests/utiltests/Android.bp core/tests/utiltests/jni/Android.bp core/tests/uwbtests/Android.bp core/xsd/Android.bp core/xsd/vts/Android.bp data/etc/Android.bp data/etc/car/Android.bp data/fonts/Android.bp data/keyboards/Android.mk drm/jni/Android.bp errorprone/Android.bp graphics/proto/Android.bp keystore/Android.bp keystore/tests/Android.bp libs/WindowManager/Jetpack/Android.bp libs/WindowManager/Shell/Android.bp libs/WindowManager/Shell/tests/Android.bp libs/androidfw/Android.bp libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp libs/hostgraphics/Android.bp libs/incident/Android.bp libs/input/Android.bp libs/input/tests/Android.bp libs/protoutil/Android.bp libs/services/Android.bp libs/storage/Android.bp libs/usb/tests/AccessoryChat/Android.bp libs/usb/tests/AccessoryChat/accessorychat/Android.bp location/lib/Android.bp location/tests/Android.bp location/tests/locationtests/Android.bp lowpan/tests/Android.bp media/Android.bp media/java/Android.bp media/java/android/media/tv/tunerresourcemanager/Android.bp media/jni/Android.bp media/jni/audioeffect/Android.bp media/jni/soundpool/Android.bp media/jni/soundpool/tests/Android.bp media/lib/remotedisplay/Android.bp media/lib/signer/Android.bp media/lib/tvremote/Android.bp media/lib/tvremote/tests/Android.bp media/mca/filterfw/Android.bp media/mca/filterfw/native/Android.bp media/mca/filterpacks/Android.bp media/mca/samples/CameraEffectsRecordingSample/Android.bp media/mca/tests/Android.bp media/native/midi/Android.bp media/packages/BluetoothMidiService/Android.bp media/packages/BluetoothMidiService/tests/unit/Android.bp media/tests/AudioPolicyTest/Android.bp media/tests/CameraBrowser/Android.bp media/tests/EffectsTest/Android.bp media/tests/MediaDump/Android.bp media/tests/MediaFrameworkTest/Android.bp media/tests/MediaRouter/Android.bp media/tests/MtpTests/Android.bp media/tests/ScoAudioTest/Android.bp media/tests/SoundPoolTest/Android.bp media/tests/TunerTest/Android.bp media/tests/audiotests/Android.bp media/tests/players/Android.bp mime/Android.bp native/android/Android.bp native/graphics/jni/Android.bp native/webview/loader/Android.bp nfc-extras/Android.bp nfc-extras/tests/Android.bp packages/AppPredictionLib/Android.bp packages/BackupEncryption/Android.bp packages/BackupEncryption/test/robolectric-integration/Android.bp packages/BackupEncryption/test/robolectric/Android.bp packages/BackupEncryption/test/unittest/Android.bp packages/BackupRestoreConfirmation/Android.bp packages/CarSystemUI/Android.bp packages/CarrierDefaultApp/Android.bp packages/CarrierDefaultApp/tests/unit/Android.bp packages/CompanionDeviceManager/Android.bp packages/Connectivity/framework/Android.bp packages/Connectivity/service/Android.bp packages/CtsShim/Android.bp packages/CtsShim/build/Android.bp packages/CtsShim/build/jni/Android.bp packages/DynamicSystemInstallationService/Android.bp packages/DynamicSystemInstallationService/tests/Android.bp packages/EasterEgg/Android.bp packages/EncryptedLocalTransport/Android.bp packages/ExtShared/Android.bp packages/ExternalStorageProvider/Android.bp packages/ExternalStorageProvider/tests/Android.bp packages/FakeOemFeatures/Android.bp packages/FusedLocation/Android.bp packages/InputDevices/Android.bp packages/LocalTransport/Android.bp packages/PackageInstaller/Android.bp packages/PrintRecommendationService/Android.bp packages/PrintSpooler/Android.bp packages/PrintSpooler/jni/Android.bp packages/PrintSpooler/tests/outofprocess/Android.bp packages/SettingsLib/ActionBarShadow/Android.bp packages/SettingsLib/ActionButtonsPreference/Android.bp packages/SettingsLib/AdaptiveIcon/Android.bp packages/SettingsLib/Android.bp packages/SettingsLib/AppPreference/Android.bp packages/SettingsLib/BarChartPreference/Android.bp packages/SettingsLib/DisplayDensityUtils/Android.bp packages/SettingsLib/EntityHeaderWidgets/Android.bp packages/SettingsLib/HelpUtils/Android.bp packages/SettingsLib/LayoutPreference/Android.bp packages/SettingsLib/ProgressBar/Android.bp packages/SettingsLib/RadioButtonPreference/Android.bp packages/SettingsLib/RestrictedLockUtils/Android.bp packages/SettingsLib/SchedulesProvider/Android.bp packages/SettingsLib/SearchProvider/Android.bp packages/SettingsLib/SearchWidget/Android.bp packages/SettingsLib/SettingsSpinner/Android.bp packages/SettingsLib/SettingsTheme/Android.bp packages/SettingsLib/Tile/Android.bp packages/SettingsLib/Utils/Android.bp packages/SettingsLib/search/Android.bp packages/SettingsLib/tests/integ/Android.bp packages/SettingsLib/tests/robotests/Android.bp packages/SettingsProvider/Android.bp packages/SharedStorageBackup/Android.bp packages/Shell/Android.bp packages/Shell/tests/Android.bp packages/SimAppDialog/Android.bp packages/SoundPicker/Android.bp packages/StatementService/Android.bp packages/SystemUI/Android.bp packages/SystemUI/plugin/Android.bp packages/SystemUI/plugin/ExamplePlugin/Android.bp packages/SystemUI/plugin_core/Android.bp packages/SystemUI/shared/Android.bp packages/VpnDialogs/Android.bp packages/WAPPushManager/Android.bp packages/WAPPushManager/tests/Android.bp packages/WallpaperBackup/Android.bp packages/WallpaperCropper/Android.bp packages/overlays/Android.mk packages/overlays/tests/Android.bp packages/services/PacProcessor/Android.bp packages/services/PacProcessor/jni/Android.bp packages/services/Proxy/Android.bp proto/Android.bp rs/jni/Android.mk samples/demo/haptic-assessment/Android.bp sax/tests/saxtests/Android.bp services/Android.bp services/accessibility/Android.bp services/appprediction/Android.bp services/appwidget/Android.bp services/autofill/Android.bp services/backup/Android.bp services/backup/backuplib/Android.bp services/companion/Android.bp services/contentcapture/Android.bp services/contentsuggestions/Android.bp services/core/Android.bp services/core/java/com/android/server/vcn/Android.bp services/core/jni/Android.bp services/core/xsd/Android.bp services/core/xsd/vts/Android.bp services/coverage/Android.bp services/devicepolicy/Android.bp services/incremental/Android.bp services/midi/Android.bp services/net/Android.bp services/people/Android.bp services/print/Android.bp services/profcollect/Android.bp services/restrictions/Android.bp services/robotests/Android.bp services/robotests/backup/Android.bp services/systemcaptions/Android.bp services/tests/PackageManagerComponentOverrideTests/Android.bp services/tests/PackageManagerServiceTests/host/Android.bp services/tests/PackageManagerServiceTests/host/test-apps/Android.bp services/tests/mockingservicestests/Android.bp services/tests/rescueparty/Android.bp services/tests/servicestests/Android.bp services/tests/servicestests/aidl/Android.bp services/tests/servicestests/apks/Android.bp services/tests/servicestests/apks/install-split-base/Android.bp services/tests/servicestests/apks/install-split-feature-a/Android.bp services/tests/servicestests/apks/install_intent_filters/Android.bp services/tests/servicestests/apks/install_uses_sdk/Android.bp services/tests/servicestests/test-apps/ConnTestApp/Android.bp services/tests/servicestests/test-apps/JobTestApp/Android.bp services/tests/servicestests/test-apps/PackageParserApp/Android.bp services/tests/servicestests/test-apps/PackageParsingTestManifests/Android.bp services/tests/servicestests/test-apps/SimpleServiceTestApp/Android.bp services/tests/servicestests/test-apps/SuspendTestApp/Android.bp services/tests/shortcutmanagerutils/Android.bp services/tests/uiservicestests/Android.bp services/tests/wmtests/Android.bp services/usage/Android.bp services/usb/Android.bp services/voiceinteraction/Android.bp services/wifi/Android.bp startop/apps/test/Android.bp startop/iorap/Android.bp startop/iorap/functional_tests/Android.bp startop/iorap/stress/Android.bp startop/iorap/tests/Android.bp startop/view_compiler/Android.bp startop/view_compiler/dex_builder_test/Android.bp test-base/hiddenapi/Android.bp test-mock/Android.bp test-runner/tests/Android.bp tests/AccessibilityEventsLogger/Android.bp tests/AccessoryDisplay/common/Android.bp tests/AccessoryDisplay/sink/Android.bp tests/AccessoryDisplay/source/Android.bp tests/ActivityManagerPerfTests/stub-app/Android.bp tests/ActivityManagerPerfTests/test-app/Android.bp tests/ActivityManagerPerfTests/tests/Android.bp tests/ActivityManagerPerfTests/utils/Android.bp tests/ActivityTests/Android.bp tests/ActivityViewTest/Android.bp tests/AmSlam/Android.bp tests/ApkVerityTest/Android.bp tests/ApkVerityTest/ApkVerityTestApp/Android.bp tests/ApkVerityTest/block_device_writer/Android.bp tests/AppLaunch/Android.bp tests/AppLaunchWear/Android.bp tests/AppResourcesLoaders/Android.bp tests/AppResourcesLoaders/Overlay/Android.bp tests/Assist/Android.bp tests/AutoVerify/app1/Android.bp tests/AutoVerify/app2/Android.bp tests/AutoVerify/app3/Android.bp tests/AutoVerify/app4/Android.bp tests/BackgroundDexOptServiceIntegrationTests/Android.bp tests/BandwidthTests/Android.bp tests/BatteryWaster/Android.bp tests/BiDiTests/Android.bp tests/BlobStoreTestUtils/Android.bp tests/BootImageProfileTest/Android.bp tests/BrowserPowerTest/Android.bp tests/Camera2Tests/SmartCamera/SimpleCamera/jni/Android.bp tests/CameraPrewarmTest/Android.bp tests/Codegen/Android.bp tests/Compatibility/Android.bp tests/CoreTests/android/Android.bp tests/DataIdleTest/Android.bp tests/DozeTest/Android.bp tests/DpiTest/Android.bp tests/DynamicCodeLoggerIntegrationTests/Android.mk tests/FeatureSplit/base/Android.bp tests/FeatureSplit/feature1/Android.bp tests/FeatureSplit/feature2/Android.bp tests/FixVibrateSetting/Android.bp tests/FlickerTests/Android.bp tests/FlickerTests/test-apps/Android.bp tests/FlickerTests/test-apps/flickerapp/Android.bp tests/FrameworkPerf/Android.bp tests/GamePerformance/Android.bp tests/GridLayoutTest/Android.bp tests/HierarchyViewerTest/Android.bp tests/HugeBackup/Android.bp tests/HwAccelerationTest/Android.bp tests/Internal/Android.bp tests/JankBench/Android.bp tests/JobSchedulerPerfTests/Android.bp tests/JobSchedulerTestApp/Android.bp tests/LargeAssetTest/Android.bp tests/LegacyAssistant/Android.bp tests/LocalizationTest/Android.bp tests/LocationTracker/Android.bp tests/LotsOfApps/Android.bp tests/LowStorageTest/Android.bp tests/ManagedProfileLifecycleStressTest/Android.bp tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp tests/MemoryUsage/Android.bp tests/MirrorSurfaceTest/Android.bp tests/NativeProcessesMemoryTest/Android.bp tests/NetworkSecurityConfigTest/Android.bp tests/NullHomeTest/Android.bp tests/OdmApps/Android.bp tests/OdmApps/app/Android.bp tests/OdmApps/priv-app/Android.bp tests/OneMedia/Android.bp tests/PackageWatchdog/Android.bp tests/PlatformCompatGating/Android.bp tests/PlatformCompatGating/test-rules/Android.bp tests/ProtoInputStreamTests/Android.bp tests/RemoteDisplayProvider/Android.bp tests/RenderThreadTest/Android.bp tests/RollbackTest/Android.bp tests/SerialChat/Android.bp tests/ServiceCrashTest/Android.bp tests/SharedLibrary/client/Android.bp tests/SharedLibrary/lib/Android.bp tests/ShowWhenLockedApp/Android.bp tests/SmokeTest/Android.bp tests/SmokeTest/tests/Android.bp tests/SmokeTestApps/Android.bp tests/SoundTriggerTestApp/Android.bp tests/Split/Android.bp tests/StagedInstallTest/Android.bp tests/StatusBar/Android.bp tests/SurfaceComposition/Android.bp tests/SurfaceControlViewHostTest/Android.bp tests/SystemMemoryTest/device/Android.bp tests/SystemMemoryTest/host/Android.bp tests/SystemUIDemoModeController/Android.bp tests/TaskOrganizerTest/Android.bp tests/TelephonyCommonTests/Android.bp tests/TouchLatency/Android.bp tests/TransformTest/Android.bp tests/TtsTests/Android.bp tests/UiBench/Android.bp tests/UsageReportingTest/Android.bp tests/UsageStatsPerfTests/Android.bp tests/UsageStatsTest/Android.bp tests/UsbHostExternalManagmentTest/AoapTestDevice/Android.bp tests/UsbHostExternalManagmentTest/AoapTestHost/Android.bp tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/Android.bp tests/UsbManagerTests/Android.bp tests/UsbManagerTests/lib/Android.bp tests/UsbTests/Android.bp tests/UsesFeature2Test/Android.bp tests/VectorDrawableTest/Android.bp tests/VoiceEnrollment/Android.bp tests/VoiceInteraction/Android.bp tests/WallpaperTest/Android.bp tests/WindowAnimationJank/Android.bp tests/WindowInsetsTests/Android.bp tests/appwidgets/AppWidgetHostTest/Android.bp tests/appwidgets/AppWidgetProviderTest/Android.bp tests/backup/Android.mk tests/benchmarks/Android.bp tests/libs-permissions/Android.bp tests/net/Android.bp tests/net/common/Android.bp tests/net/deflake/Android.bp tests/net/integration/Android.bp tests/net/jni/Android.bp tests/net/smoketest/Android.bp tests/notification/Android.bp tests/permission/Android.bp tests/privapp-permissions/Android.bp tests/testables/Android.bp tests/testables/tests/Android.bp tests/utils/StubIME/Android.bp tests/utils/hostutils/Android.bp tests/utils/testutils/Android.bp tests/vcn/Android.bp tools/aapt/Android.bp tools/aapt2/Android.bp tools/aapt2/integration-tests/AutoVersionTest/Android.bp tools/aapt2/integration-tests/BasicTest/Android.bp tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk tools/aapt2/integration-tests/StaticLibTest/App/Android.bp tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp tools/aapt2/integration-tests/SymlinkTest/Android.bp tools/bit/Android.bp tools/codegen/Android.bp tools/dump-coverage/Android.bp tools/incident_report/Android.bp tools/incident_section_gen/Android.bp tools/lock_agent/Android.bp tools/locked_region_code_injection/Android.bp tools/obbtool/Android.bp tools/powermodel/Android.bp tools/preload-check/Android.bp tools/preload-check/device/Android.bp tools/preload/loadclass/Android.bp tools/processors/staledataclass/Android.bp tools/processors/view_inspector/Android.bp tools/protologtool/Android.bp tools/sdkparcelables/Android.bp tools/split-select/Android.bp tools/streaming_proto/Android.bp tools/validatekeymaps/Android.bp wifi/java/Android.bp wifi/tests/Android.bp Added SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-BSD to: libs/hwui/Android.bp native/webview/plat_support/Android.bp obex/Android.bp Added SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-BSD SPDX-license-identifier-CC-BY SPDX-license-identifier-CPL-1.0 SPDX-license-identifier-GPL SPDX-license-identifier-GPL-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS SPDX-license-identifier-W3C legacy_unencumbered to: Android.bp Added SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-BSD legacy_unencumbered to: core/java/Android.bp Added SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-CPL-1.0 to: test-base/Android.bp test-runner/Android.bp Added SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-GPL to: core/res/Android.bp Added SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-GPL-2.0 to: libs/usb/Android.bp libs/usb/tests/accessorytest/Android.bp Added SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT to: tools/preload/Android.bp Added SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS to: api/Android.bp boot/Android.bp cmds/device_config/Android.bp cmds/settings/Android.bp core/api/Android.bp core/tests/coretests/certs/Android.bp core/tests/overlaytests/remount/test-apps/certs/Android.bp core/tests/overlaytests/remount/test-apps/overlaid_apex/Android.bp core/tests/overlaytests/remount/test-apps/overlay_apex/Android.bp libs/tracingproxy/Android.bp services/startop/Android.bp test-legacy/Android.mk tests/ApkVerityTest/testdata/Android.bp tests/TransitionTests/Android.bp Bug: 68860345 Bug: 151177513 Bug: 151953481 Test: m all Exempt-From-Owner-Approval: janitorial work Change-Id: Ic44d662936d1ff0cae7fbe915932b37aa4e4869a Merged-in: I6e9103c3275cb2e6df5dc586588eccd7d2ab6b06 --- framework/Android.bp | 11 ++++++++++- service/Android.bp | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/framework/Android.bp b/framework/Android.bp index 8db8d7699a..3326ea9edd 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -15,6 +15,15 @@ // // TODO: use a java_library in the bootclasspath instead +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + filegroup { name: "framework-connectivity-sources", srcs: [ @@ -26,4 +35,4 @@ filegroup { "//frameworks/base", "//packages/modules/Connectivity:__subpackages__", ], -} \ No newline at end of file +} diff --git a/service/Android.bp b/service/Android.bp index ed1716fad8..f20b89fb84 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -14,6 +14,15 @@ // limitations under the License. // +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + cc_library_shared { name: "libservice-connectivity", // TODO: build against the NDK (sdk_version: "30" for example) From 6e9ae2339b87ea6953beca8b0e552fd0b1e561eb Mon Sep 17 00:00:00 2001 From: Hai Shalom Date: Wed, 10 Feb 2021 14:26:13 -0800 Subject: [PATCH 039/232] Clean up T&C implementation Update the T&C variable names internally to user portal, to have a consistent naming. Bug: 162785447 Test: compiles - no logic changes Change-Id: Ie9b0a2630952c2d82606c1ecd089b2131edbcefc --- .../src/android/net/CaptivePortalData.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/framework/src/android/net/CaptivePortalData.java b/framework/src/android/net/CaptivePortalData.java index f4b46e9f11..eafda4d2d6 100644 --- a/framework/src/android/net/CaptivePortalData.java +++ b/framework/src/android/net/CaptivePortalData.java @@ -44,7 +44,7 @@ public final class CaptivePortalData implements Parcelable { private final boolean mCaptive; private final String mVenueFriendlyName; private final int mVenueInfoUrlSource; - private final int mTermsAndConditionsSource; + private final int mUserPortalUrlSource; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -65,7 +65,7 @@ public final class CaptivePortalData implements Parcelable { private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive, - String venueFriendlyName, int venueInfoUrlSource, int termsAndConditionsSource) { + String venueFriendlyName, int venueInfoUrlSource, int userPortalUrlSource) { mRefreshTimeMillis = refreshTimeMillis; mUserPortalUrl = userPortalUrl; mVenueInfoUrl = venueInfoUrl; @@ -75,7 +75,7 @@ public final class CaptivePortalData implements Parcelable { mCaptive = captive; mVenueFriendlyName = venueFriendlyName; mVenueInfoUrlSource = venueInfoUrlSource; - mTermsAndConditionsSource = termsAndConditionsSource; + mUserPortalUrlSource = userPortalUrlSource; } private CaptivePortalData(Parcel p) { @@ -100,7 +100,7 @@ public final class CaptivePortalData implements Parcelable { dest.writeBoolean(mCaptive); dest.writeString(mVenueFriendlyName); dest.writeInt(mVenueInfoUrlSource); - dest.writeInt(mTermsAndConditionsSource); + dest.writeInt(mUserPortalUrlSource); } /** @@ -130,7 +130,7 @@ public final class CaptivePortalData implements Parcelable { public Builder(@Nullable CaptivePortalData data) { if (data == null) return; setRefreshTime(data.mRefreshTimeMillis) - .setUserPortalUrl(data.mUserPortalUrl, data.mTermsAndConditionsSource) + .setUserPortalUrl(data.mUserPortalUrl, data.mUserPortalUrlSource) .setVenueInfoUrl(data.mVenueInfoUrl, data.mVenueInfoUrlSource) .setSessionExtendable(data.mIsSessionExtendable) .setBytesRemaining(data.mByteLimit) @@ -314,7 +314,7 @@ public final class CaptivePortalData implements Parcelable { * @return The source that the user portal URL was obtained from */ public @CaptivePortalDataSource int getUserPortalUrlSource() { - return mTermsAndConditionsSource; + return mUserPortalUrlSource; } /** @@ -342,7 +342,7 @@ public final class CaptivePortalData implements Parcelable { public int hashCode() { return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl, mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName, - mVenueInfoUrlSource, mTermsAndConditionsSource); + mVenueInfoUrlSource, mUserPortalUrlSource); } @Override @@ -358,7 +358,7 @@ public final class CaptivePortalData implements Parcelable { && mCaptive == other.mCaptive && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName) && mVenueInfoUrlSource == other.mVenueInfoUrlSource - && mTermsAndConditionsSource == other.mTermsAndConditionsSource; + && mUserPortalUrlSource == other.mUserPortalUrlSource; } @Override @@ -373,7 +373,7 @@ public final class CaptivePortalData implements Parcelable { + ", captive: " + mCaptive + ", venueFriendlyName: " + mVenueFriendlyName + ", venueInfoUrlSource: " + mVenueInfoUrlSource - + ", termsAndConditionsSource: " + mTermsAndConditionsSource + + ", userPortalUrlSource: " + mUserPortalUrlSource + "}"; } } From 71ced8e08877ef1526ce3f6a3ce8b8b4257e8e7e Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 15 Feb 2021 18:52:06 +0900 Subject: [PATCH 040/232] Do not depend on Tethering stubs in CM consts Tethering API stubs depend on connectivity stubs for classes like MacAddress or LinkAddress, so connectivity stubs cannot depend on Tethering stubs or there would be a circular dependency. This means ConnectivityManager API surface cannot reference Tethering API constants. Instead, use the literal in ConnectivityManager. This means that both ConnectivityManager and TetheringManager specify the constant value. An alternative considered was to have TetheringManager depend on the ConnectivityManager constants, but considering that ConnectivityManager only has some of the constants, this would be more confusing. Breaking the constants by mistake is unlikely as their values are part of the API surface, so will always be in sync. Bug: 171540887 Test: m Change-Id: I16b6e1912fffc5ff8b3b392901d2357ffd213c72 --- framework/src/android/net/ConnectivityManager.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 4213f8af95..c7bb2a75db 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -456,7 +456,7 @@ public class ConnectivityManager { * @hide */ @SystemApi - public static final int TETHERING_WIFI = TetheringManager.TETHERING_WIFI; + public static final int TETHERING_WIFI = 0; /** * USB tethering type. @@ -464,7 +464,7 @@ public class ConnectivityManager { * @hide */ @SystemApi - public static final int TETHERING_USB = TetheringManager.TETHERING_USB; + public static final int TETHERING_USB = 1; /** * Bluetooth tethering type. @@ -472,7 +472,7 @@ public class ConnectivityManager { * @hide */ @SystemApi - public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH; + public static final int TETHERING_BLUETOOTH = 2; /** * Wifi P2p tethering type. @@ -2799,7 +2799,7 @@ public class ConnectivityManager { */ @SystemApi @Deprecated - public static final int TETHER_ERROR_NO_ERROR = TetheringManager.TETHER_ERROR_NO_ERROR; + public static final int TETHER_ERROR_NO_ERROR = 0; /** * @deprecated Use {@link TetheringManager#TETHER_ERROR_UNKNOWN_IFACE}. * {@hide} @@ -2875,8 +2875,7 @@ public class ConnectivityManager { */ @SystemApi @Deprecated - public static final int TETHER_ERROR_PROVISION_FAILED = - TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; + public static final int TETHER_ERROR_PROVISION_FAILED = 11; /** * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}. * {@hide} @@ -2890,8 +2889,7 @@ public class ConnectivityManager { */ @SystemApi @Deprecated - public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = - TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; + public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; /** * Get a more detailed error code after a Tethering or Untethering From 6916d5194b3f24c1f0cb038a1f77b27e3a9c7257 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 15 Feb 2021 20:16:28 +0900 Subject: [PATCH 041/232] Split parcelable .aidl files to aidl-export The one-line "parcelable X" files need to be imported by targets that do not build against SDK (the SDK has prebuilt definitions), so prepare a dedicated directory for them. This avoids having users of the classes include the whole src/ directory, which could contain definitions for classes that are not part of the public API, so should not be imported. Also move back to frameworks/base/core some .aidl definitions that were separated from their associated class. Bug: 171540887 Test: m Merged-In: I7432fe4c87cd3cab04dcb6185c9a4f3f84376549 Change-Id: I7432fe4c87cd3cab04dcb6185c9a4f3f84376549 --- framework/Android.bp | 27 +++++++++++++++++-- .../android/net/CaptivePortalData.aidl | 0 .../net/ConnectivityDiagnosticsManager.aidl | 0 .../android/net/DhcpInfo.aidl | 0 .../android/net/IpConfiguration.aidl | 0 .../android/net/IpPrefix.aidl | 0 .../android/net/KeepalivePacketData.aidl | 0 .../android/net/LinkAddress.aidl | 0 .../android/net/LinkProperties.aidl | 0 .../android/net/MacAddress.aidl | 0 .../android/net/Network.aidl | 0 .../android/net/NetworkAgentConfig.aidl | 0 .../android/net/NetworkCapabilities.aidl | 0 .../android/net/NetworkInfo.aidl | 0 .../android/net/NetworkRequest.aidl | 0 .../android/net/ProxyInfo.aidl | 0 .../android/net/RouteInfo.aidl | 0 .../android/net/StaticIpConfiguration.aidl | 0 .../android/net/TestNetworkInterface.aidl | 0 .../android/net/apf/ApfCapabilities.aidl | 0 .../android/net/ConnectivityMetricsEvent.aidl | 20 -------------- .../android/net/InterfaceConfiguration.aidl | 19 ------------- framework/src/android/net/UidRange.aidl | 24 ----------------- 23 files changed, 25 insertions(+), 65 deletions(-) rename framework/{src => aidl-export}/android/net/CaptivePortalData.aidl (100%) rename framework/{src => aidl-export}/android/net/ConnectivityDiagnosticsManager.aidl (100%) rename framework/{src => aidl-export}/android/net/DhcpInfo.aidl (100%) rename framework/{src => aidl-export}/android/net/IpConfiguration.aidl (100%) rename framework/{src => aidl-export}/android/net/IpPrefix.aidl (100%) rename framework/{src => aidl-export}/android/net/KeepalivePacketData.aidl (100%) rename framework/{src => aidl-export}/android/net/LinkAddress.aidl (100%) rename framework/{src => aidl-export}/android/net/LinkProperties.aidl (100%) rename framework/{src => aidl-export}/android/net/MacAddress.aidl (100%) rename framework/{src => aidl-export}/android/net/Network.aidl (100%) rename framework/{src => aidl-export}/android/net/NetworkAgentConfig.aidl (100%) rename framework/{src => aidl-export}/android/net/NetworkCapabilities.aidl (100%) rename framework/{src => aidl-export}/android/net/NetworkInfo.aidl (100%) rename framework/{src => aidl-export}/android/net/NetworkRequest.aidl (100%) rename framework/{src => aidl-export}/android/net/ProxyInfo.aidl (100%) rename framework/{src => aidl-export}/android/net/RouteInfo.aidl (100%) rename framework/{src => aidl-export}/android/net/StaticIpConfiguration.aidl (100%) rename framework/{src => aidl-export}/android/net/TestNetworkInterface.aidl (100%) rename framework/{src => aidl-export}/android/net/apf/ApfCapabilities.aidl (100%) delete mode 100644 framework/src/android/net/ConnectivityMetricsEvent.aidl delete mode 100644 framework/src/android/net/InterfaceConfiguration.aidl delete mode 100644 framework/src/android/net/UidRange.aidl diff --git a/framework/Android.bp b/framework/Android.bp index 3326ea9edd..ffca971430 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -14,7 +14,6 @@ // limitations under the License. // -// TODO: use a java_library in the bootclasspath instead package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import @@ -24,13 +23,37 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +// TODO: use a java_library in the bootclasspath instead filegroup { - name: "framework-connectivity-sources", + name: "framework-connectivity-internal-sources", srcs: [ "src/**/*.java", "src/**/*.aidl", ], path: "src", + visibility: [ + "//visibility:private", + ], +} + +filegroup { + name: "framework-connectivity-aidl-export-sources", + srcs: [ + "aidl-export/**/*.aidl", + ], + path: "aidl-export", + visibility: [ + "//visibility:private", + ], +} + +// TODO: use a java_library in the bootclasspath instead +filegroup { + name: "framework-connectivity-sources", + srcs: [ + ":framework-connectivity-internal-sources", + ":framework-connectivity-aidl-export-sources", + ], visibility: [ "//frameworks/base", "//packages/modules/Connectivity:__subpackages__", diff --git a/framework/src/android/net/CaptivePortalData.aidl b/framework/aidl-export/android/net/CaptivePortalData.aidl similarity index 100% rename from framework/src/android/net/CaptivePortalData.aidl rename to framework/aidl-export/android/net/CaptivePortalData.aidl diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.aidl b/framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl similarity index 100% rename from framework/src/android/net/ConnectivityDiagnosticsManager.aidl rename to framework/aidl-export/android/net/ConnectivityDiagnosticsManager.aidl diff --git a/framework/src/android/net/DhcpInfo.aidl b/framework/aidl-export/android/net/DhcpInfo.aidl similarity index 100% rename from framework/src/android/net/DhcpInfo.aidl rename to framework/aidl-export/android/net/DhcpInfo.aidl diff --git a/framework/src/android/net/IpConfiguration.aidl b/framework/aidl-export/android/net/IpConfiguration.aidl similarity index 100% rename from framework/src/android/net/IpConfiguration.aidl rename to framework/aidl-export/android/net/IpConfiguration.aidl diff --git a/framework/src/android/net/IpPrefix.aidl b/framework/aidl-export/android/net/IpPrefix.aidl similarity index 100% rename from framework/src/android/net/IpPrefix.aidl rename to framework/aidl-export/android/net/IpPrefix.aidl diff --git a/framework/src/android/net/KeepalivePacketData.aidl b/framework/aidl-export/android/net/KeepalivePacketData.aidl similarity index 100% rename from framework/src/android/net/KeepalivePacketData.aidl rename to framework/aidl-export/android/net/KeepalivePacketData.aidl diff --git a/framework/src/android/net/LinkAddress.aidl b/framework/aidl-export/android/net/LinkAddress.aidl similarity index 100% rename from framework/src/android/net/LinkAddress.aidl rename to framework/aidl-export/android/net/LinkAddress.aidl diff --git a/framework/src/android/net/LinkProperties.aidl b/framework/aidl-export/android/net/LinkProperties.aidl similarity index 100% rename from framework/src/android/net/LinkProperties.aidl rename to framework/aidl-export/android/net/LinkProperties.aidl diff --git a/framework/src/android/net/MacAddress.aidl b/framework/aidl-export/android/net/MacAddress.aidl similarity index 100% rename from framework/src/android/net/MacAddress.aidl rename to framework/aidl-export/android/net/MacAddress.aidl diff --git a/framework/src/android/net/Network.aidl b/framework/aidl-export/android/net/Network.aidl similarity index 100% rename from framework/src/android/net/Network.aidl rename to framework/aidl-export/android/net/Network.aidl diff --git a/framework/src/android/net/NetworkAgentConfig.aidl b/framework/aidl-export/android/net/NetworkAgentConfig.aidl similarity index 100% rename from framework/src/android/net/NetworkAgentConfig.aidl rename to framework/aidl-export/android/net/NetworkAgentConfig.aidl diff --git a/framework/src/android/net/NetworkCapabilities.aidl b/framework/aidl-export/android/net/NetworkCapabilities.aidl similarity index 100% rename from framework/src/android/net/NetworkCapabilities.aidl rename to framework/aidl-export/android/net/NetworkCapabilities.aidl diff --git a/framework/src/android/net/NetworkInfo.aidl b/framework/aidl-export/android/net/NetworkInfo.aidl similarity index 100% rename from framework/src/android/net/NetworkInfo.aidl rename to framework/aidl-export/android/net/NetworkInfo.aidl diff --git a/framework/src/android/net/NetworkRequest.aidl b/framework/aidl-export/android/net/NetworkRequest.aidl similarity index 100% rename from framework/src/android/net/NetworkRequest.aidl rename to framework/aidl-export/android/net/NetworkRequest.aidl diff --git a/framework/src/android/net/ProxyInfo.aidl b/framework/aidl-export/android/net/ProxyInfo.aidl similarity index 100% rename from framework/src/android/net/ProxyInfo.aidl rename to framework/aidl-export/android/net/ProxyInfo.aidl diff --git a/framework/src/android/net/RouteInfo.aidl b/framework/aidl-export/android/net/RouteInfo.aidl similarity index 100% rename from framework/src/android/net/RouteInfo.aidl rename to framework/aidl-export/android/net/RouteInfo.aidl diff --git a/framework/src/android/net/StaticIpConfiguration.aidl b/framework/aidl-export/android/net/StaticIpConfiguration.aidl similarity index 100% rename from framework/src/android/net/StaticIpConfiguration.aidl rename to framework/aidl-export/android/net/StaticIpConfiguration.aidl diff --git a/framework/src/android/net/TestNetworkInterface.aidl b/framework/aidl-export/android/net/TestNetworkInterface.aidl similarity index 100% rename from framework/src/android/net/TestNetworkInterface.aidl rename to framework/aidl-export/android/net/TestNetworkInterface.aidl diff --git a/framework/src/android/net/apf/ApfCapabilities.aidl b/framework/aidl-export/android/net/apf/ApfCapabilities.aidl similarity index 100% rename from framework/src/android/net/apf/ApfCapabilities.aidl rename to framework/aidl-export/android/net/apf/ApfCapabilities.aidl diff --git a/framework/src/android/net/ConnectivityMetricsEvent.aidl b/framework/src/android/net/ConnectivityMetricsEvent.aidl deleted file mode 100644 index 1c541dc4c8..0000000000 --- a/framework/src/android/net/ConnectivityMetricsEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -/** {@hide} */ -parcelable ConnectivityMetricsEvent; diff --git a/framework/src/android/net/InterfaceConfiguration.aidl b/framework/src/android/net/InterfaceConfiguration.aidl deleted file mode 100644 index 8aa5e34528..0000000000 --- a/framework/src/android/net/InterfaceConfiguration.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2008, 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; - -parcelable InterfaceConfiguration; diff --git a/framework/src/android/net/UidRange.aidl b/framework/src/android/net/UidRange.aidl deleted file mode 100644 index f70fc8e2fe..0000000000 --- a/framework/src/android/net/UidRange.aidl +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2018 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; - -/** - * An inclusive range of UIDs. - * - * {@hide} - */ -parcelable UidRange; \ No newline at end of file From 709eb8461294937122682aa8a8f124b7508fcaea Mon Sep 17 00:00:00 2001 From: lucaslin Date: Thu, 21 Jan 2021 02:04:15 +0800 Subject: [PATCH 042/232] [IT4.6] Unbundle NMS out from ConnectivityManager ConnectivityService is no longer to update idle timer to NMS but send to INetd directly after this change. Replace the API implementation in ConnectivityManager to refer into ConnectivityService instead of NetworkManagementService to remove the dependency between CM and NMS for ConnectivityService mainline. Bug: 170598012 Test: atest FrameworksNetTests Change-Id: If0ac9a6427dba5a732a15b5d7ca1351b71b07b7b --- .../src/android/net/ConnectivityManager.java | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index c7bb2a75db..adab6e3fbc 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -50,7 +50,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.INetworkActivityListener; -import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.Messenger; @@ -835,7 +834,6 @@ public class ConnectivityManager { private final Context mContext; - private INetworkManagementService mNMService; private INetworkPolicyManager mNPManager; private final TetheringManager mTetheringManager; @@ -2211,17 +2209,6 @@ public class ConnectivityManager { void onNetworkActive(); } - private INetworkManagementService getNetworkManagementService() { - synchronized (this) { - if (mNMService != null) { - return mNMService; - } - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - mNMService = INetworkManagementService.Stub.asInterface(b); - return mNMService; - } - } - private final ArrayMap mNetworkActivityListeners = new ArrayMap<>(); @@ -2246,7 +2233,7 @@ public class ConnectivityManager { }; try { - getNetworkManagementService().registerNetworkActivityListener(rl); + mService.registerNetworkActivityListener(rl); mNetworkActivityListeners.put(l, rl); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2263,7 +2250,7 @@ public class ConnectivityManager { INetworkActivityListener rl = mNetworkActivityListeners.get(l); Preconditions.checkArgument(rl != null, "Listener was not registered."); try { - getNetworkManagementService().unregisterNetworkActivityListener(rl); + mService.registerNetworkActivityListener(rl); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2279,7 +2266,7 @@ public class ConnectivityManager { */ public boolean isDefaultNetworkActive() { try { - return getNetworkManagementService().isNetworkActive(); + return mService.isDefaultNetworkActive(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } From cea06a618b62143027920f6a2b5370ef6a9546db Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Thu, 4 Feb 2021 17:29:59 +0800 Subject: [PATCH 043/232] [IT06]Move INetworkActivityListener into connectivity module INetworkActivityListener is hidden and the only usage is inside the connectivity module. Thus, move this into module scope. Bug: 170598012 Test: atest FrameworksNetTests Change-Id: I0a75c440c1daa773217bbd362b212fda4d07ec64 --- .../src/android/net/ConnectivityManager.java | 1 - .../src/android/net/IConnectivityManager.aidl | 2 +- .../android/net/INetworkActivityListener.aidl | 24 +++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 framework/src/android/net/INetworkActivityListener.aidl diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index adab6e3fbc..f41a8cb14d 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -49,7 +49,6 @@ import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.INetworkActivityListener; import android.os.Looper; import android.os.Message; import android.os.Messenger; diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 6391802f33..160338d396 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -21,6 +21,7 @@ import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; import android.net.IOnSetOemNetworkPreferenceListener; +import android.net.INetworkActivityListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; @@ -36,7 +37,6 @@ import android.net.UidRange; import android.net.QosSocketInfo; import android.os.Bundle; import android.os.IBinder; -import android.os.INetworkActivityListener; import android.os.Messenger; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; diff --git a/framework/src/android/net/INetworkActivityListener.aidl b/framework/src/android/net/INetworkActivityListener.aidl new file mode 100644 index 0000000000..79687dd3bf --- /dev/null +++ b/framework/src/android/net/INetworkActivityListener.aidl @@ -0,0 +1,24 @@ +/* Copyright 2013, 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; + +/** + * @hide + */ +oneway interface INetworkActivityListener +{ + void onNetworkActive(); +} From 345c2dfcf046d74c6e0fd5335fab000ad6c6bbcb Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 3 Feb 2021 10:18:20 +0900 Subject: [PATCH 044/232] Use formal API for ActivityThread to set proxy Add setHttpProxyConfiguration to the public API, and use ConnectivityManager APIs from ActivityThread (instead of hidden APIs) to get/set the proxy for an app process. The default proxy is now initialized with getDefaultProxy instead of getProxyForNetwork(null); this should not make a difference, as nothing should have called bindProcessToNetwork at that point yet. Bug: 174436414 Test: m; device boots Merged-In: Ifb516194ecde1567cea4b6806946091cdcf2f015 Change-Id: I06b797eeae54609aecdc0afe1df4e6c602a17a69 --- .../src/android/net/ConnectivityManager.java | 2 +- framework/src/android/net/Proxy.java | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 4213f8af95..6b4e524410 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4608,7 +4608,7 @@ public class ConnectivityManager { // Set HTTP proxy system properties to match network. // TODO: Deprecate this static method and replace it with a non-static version. try { - Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy()); + Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy()); } catch (SecurityException e) { // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy. Log.e(TAG, "Can't set proxy properties", e); diff --git a/framework/src/android/net/Proxy.java b/framework/src/android/net/Proxy.java index 9cd7ab2c3e..77c8a4f457 100644 --- a/framework/src/android/net/Proxy.java +++ b/framework/src/android/net/Proxy.java @@ -16,8 +16,10 @@ package android.net; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; @@ -185,7 +187,19 @@ public final class Proxy { /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final void setHttpProxySystemProperty(ProxyInfo p) { + @Deprecated + public static void setHttpProxySystemProperty(ProxyInfo p) { + setHttpProxyConfiguration(p); + } + + /** + * Set HTTP proxy configuration for the process to match the provided ProxyInfo. + * + * If the provided ProxyInfo is null, the proxy configuration will be cleared. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static void setHttpProxyConfiguration(@Nullable ProxyInfo p) { String host = null; String port = null; String exclList = null; @@ -196,11 +210,11 @@ public final class Proxy { exclList = ProxyUtils.exclusionListAsString(p.getExclusionList()); pacFileUrl = p.getPacFileUrl(); } - setHttpProxySystemProperty(host, port, exclList, pacFileUrl); + setHttpProxyConfiguration(host, port, exclList, pacFileUrl); } /** @hide */ - public static final void setHttpProxySystemProperty(String host, String port, String exclList, + public static void setHttpProxyConfiguration(String host, String port, String exclList, Uri pacFileUrl) { if (exclList != null) exclList = exclList.replace(",", "|"); if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList); From e849277786bf9ec0514cbd53e8b7ed9ef5c6af5f Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 18 Feb 2021 01:17:36 +0900 Subject: [PATCH 045/232] Delete VPN methods in ConnectivityManager. 1. Stop using ConnectivityManager for VPNs in VpnDialogs. 2. Delete updateLockdownVpn, since all callers have been migrated to calling VpnManager directly. 3. Delete the call to VpnManager in factoryReset, since the only caller (ResetNetworkConfirm) has been updated to call into VpnManager directly. 4. Delete getVpnManager, since it is now unused. Test: m Bug: 173331190 Change-Id: I5d071281c0e36f6523fea10671a9abf994c66d66 --- .../src/android/net/ConnectivityManager.java | 73 ------------------- 1 file changed, 73 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index d3414a404d..071ec34b62 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1069,58 +1069,6 @@ public class ConnectivityManager { } } - /** - * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); - } - - /** - * Calls VpnManager#setAlwaysOnVpnPackageForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, - boolean lockdownEnabled, @Nullable List lockdownAllowlist) { - return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, - lockdownAllowlist); - } - - /** - * Calls VpnManager#getAlwaysOnVpnPackageForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public String getAlwaysOnVpnPackageForUser(int userId) { - return getVpnManager().getAlwaysOnVpnPackageForUser(userId); - } - - /** - * Calls VpnManager#isVpnLockdownEnabled. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean isVpnLockdownEnabled(int userId) { - return getVpnManager().isVpnLockdownEnabled(userId); - } - - /** - * Calls VpnManager#getVpnLockdownAllowlist. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public List getVpnLockdownAllowlist(int userId) { - return getVpnManager().getVpnLockdownAllowlist(userId); - } - /** * Adds or removes a requirement for given UID ranges to use the VPN. * @@ -3166,16 +3114,6 @@ public class ConnectivityManager { } } - /** - * Calls VpnManager#updateLockdownVpn. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean updateLockdownVpn() { - return getVpnManager().updateLockdownVpn(); - } - /** * Set sign in error notification to visible or invisible * @@ -4537,8 +4475,6 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); - // TODO: Migrate callers to VpnManager#factoryReset. - getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4832,15 +4768,6 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } - /** - * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a - * private final mVpnManager because ConnectivityManager is initialized before VpnManager. - * @hide TODO: remove. - */ - public VpnManager getVpnManager() { - return mContext.getSystemService(VpnManager.class); - } - /** @hide */ public ConnectivityDiagnosticsManager createDiagnosticsManager() { return new ConnectivityDiagnosticsManager(mContext, mService); From da6bc5ace2f63221e7cfe9c909af7e0a932415fb Mon Sep 17 00:00:00 2001 From: Nataniel Borges Date: Fri, 19 Feb 2021 15:25:33 +0000 Subject: [PATCH 046/232] Revert "Delete VPN methods in ConnectivityManager." This reverts commit e849277786bf9ec0514cbd53e8b7ed9ef5c6af5f. Bug: 180710918 Reason for revert: Broken build Change-Id: I6e6318b4c01ccab27ca3e9f16bb2194aa1d9ffd2 --- .../src/android/net/ConnectivityManager.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 071ec34b62..d3414a404d 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1069,6 +1069,58 @@ public class ConnectivityManager { } } + /** + * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide + */ + @Deprecated + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); + } + + /** + * Calls VpnManager#setAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide + */ + @Deprecated + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List lockdownAllowlist) { + return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, + lockdownAllowlist); + } + + /** + * Calls VpnManager#getAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide + */ + @Deprecated + public String getAlwaysOnVpnPackageForUser(int userId) { + return getVpnManager().getAlwaysOnVpnPackageForUser(userId); + } + + /** + * Calls VpnManager#isVpnLockdownEnabled. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide + */ + @Deprecated + public boolean isVpnLockdownEnabled(int userId) { + return getVpnManager().isVpnLockdownEnabled(userId); + } + + /** + * Calls VpnManager#getVpnLockdownAllowlist. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide + */ + @Deprecated + public List getVpnLockdownAllowlist(int userId) { + return getVpnManager().getVpnLockdownAllowlist(userId); + } + /** * Adds or removes a requirement for given UID ranges to use the VPN. * @@ -3114,6 +3166,16 @@ public class ConnectivityManager { } } + /** + * Calls VpnManager#updateLockdownVpn. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide + */ + @Deprecated + public boolean updateLockdownVpn() { + return getVpnManager().updateLockdownVpn(); + } + /** * Set sign in error notification to visible or invisible * @@ -4475,6 +4537,8 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); + // TODO: Migrate callers to VpnManager#factoryReset. + getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4768,6 +4832,15 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } + /** + * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a + * private final mVpnManager because ConnectivityManager is initialized before VpnManager. + * @hide TODO: remove. + */ + public VpnManager getVpnManager() { + return mContext.getSystemService(VpnManager.class); + } + /** @hide */ public ConnectivityDiagnosticsManager createDiagnosticsManager() { return new ConnectivityDiagnosticsManager(mContext, mService); From b2966facbb3be5aa7a6f6dbcf18c3275ae9375fd Mon Sep 17 00:00:00 2001 From: junyulai Date: Wed, 13 Jan 2021 18:13:11 +0800 Subject: [PATCH 047/232] [VCN07] Bypass VCN for non-internet app accessible cellular services Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities and user intention, which includes: 1. For the requests that don't have anything besides VCN_SUPPORTED_CAPABILITIES, add the NOT_VCN_MANAGED to allow the callers automatically utilize VCN networks if available. 2. For the requests that explicitly add or remove NOT_VCN_MANAGED, do not alter them to allow user fire request that suits their need. Test: atest NetworkRequestTest#testBypassingVcnForNonInternetRequest Bug: 175662146 Change-Id: I2876264cee14b624c89ba3b380027a8b521ad8ea (cherry-picked from aosp/1549817) --- framework/src/android/net/NetworkRequest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 4e3085f470..b4a651c060 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -16,6 +16,22 @@ package android.net; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -30,6 +46,8 @@ import android.os.Process; import android.text.TextUtils; import android.util.proto.ProtoOutputStream; +import java.util.Arrays; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -154,8 +172,30 @@ public class NetworkRequest implements Parcelable { * needed in terms of {@link NetworkCapabilities} features */ public static class Builder { + /** + * Capabilities that are currently compatible with VCN networks. + */ + private static final List VCN_SUPPORTED_CAPABILITIES = Arrays.asList( + NET_CAPABILITY_CAPTIVE_PORTAL, + NET_CAPABILITY_DUN, + NET_CAPABILITY_FOREGROUND, + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_NOT_CONGESTED, + NET_CAPABILITY_NOT_METERED, + NET_CAPABILITY_NOT_RESTRICTED, + NET_CAPABILITY_NOT_ROAMING, + NET_CAPABILITY_NOT_SUSPENDED, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NET_CAPABILITY_TEMPORARILY_NOT_METERED, + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_VALIDATED); + private final NetworkCapabilities mNetworkCapabilities; + // A boolean that represents the user modified NOT_VCN_MANAGED capability. + private boolean mModifiedNotVcnManaged = false; + /** * Default constructor for Builder. */ @@ -177,6 +217,7 @@ public class NetworkRequest implements Parcelable { // maybeMarkCapabilitiesRestricted() doesn't add back. final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities); nc.maybeMarkCapabilitiesRestricted(); + deduceNotVcnManagedCapability(nc); return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE, ConnectivityManager.REQUEST_ID_UNSET, Type.NONE); } @@ -193,6 +234,9 @@ public class NetworkRequest implements Parcelable { */ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addCapability(capability); + if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { + mModifiedNotVcnManaged = true; + } return this; } @@ -204,6 +248,9 @@ public class NetworkRequest implements Parcelable { */ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeCapability(capability); + if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { + mModifiedNotVcnManaged = true; + } return this; } @@ -261,6 +308,9 @@ public class NetworkRequest implements Parcelable { @NonNull public Builder clearCapabilities() { mNetworkCapabilities.clearAll(); + // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities + // should not be add back later. + mModifiedNotVcnManaged = true; return this; } @@ -380,6 +430,25 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setSignalStrength(signalStrength); return this; } + + /** + * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities + * and user intention, which includes: + * 1. For the requests that don't have anything besides + * {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to + * allow the callers automatically utilize VCN networks if available. + * 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED, + * do not alter them to allow user fire request that suits their need. + * + * @hide + */ + private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) { + if (mModifiedNotVcnManaged) return; + for (final int cap : nc.getCapabilities()) { + if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return; + } + nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + } } // implement the Parcelable interface From a7f938e4aa7a06e25318c317c420c89b1d57fba6 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 22 Feb 2021 03:18:42 +0000 Subject: [PATCH 048/232] Revert "Revert "Delete VPN methods in ConnectivityManager."" 1. Stop using ConnectivityManager for VPNs in VpnDialogs. 2. Delete updateLockdownVpn, since all callers have been migrated to calling VpnManager directly. 3. Delete the call to VpnManager in factoryReset, since the only caller (ResetNetworkConfirm) has been updated to call into VpnManager directly. 4. Delete getVpnManager, since it is now unused. This reverts commit da6bc5ace2f63221e7cfe9c909af7e0a932415fb. Reason for revert: should be safe to submit now that aosp/1596096 is merged Bug: 173331190 Test: treehugger Change-Id: Ife3607c024006ce4fe46c981e9742170becb6331 --- .../src/android/net/ConnectivityManager.java | 73 ------------------- 1 file changed, 73 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index d3414a404d..071ec34b62 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1069,58 +1069,6 @@ public class ConnectivityManager { } } - /** - * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); - } - - /** - * Calls VpnManager#setAlwaysOnVpnPackageForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, - boolean lockdownEnabled, @Nullable List lockdownAllowlist) { - return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, - lockdownAllowlist); - } - - /** - * Calls VpnManager#getAlwaysOnVpnPackageForUser. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public String getAlwaysOnVpnPackageForUser(int userId) { - return getVpnManager().getAlwaysOnVpnPackageForUser(userId); - } - - /** - * Calls VpnManager#isVpnLockdownEnabled. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean isVpnLockdownEnabled(int userId) { - return getVpnManager().isVpnLockdownEnabled(userId); - } - - /** - * Calls VpnManager#getVpnLockdownAllowlist. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public List getVpnLockdownAllowlist(int userId) { - return getVpnManager().getVpnLockdownAllowlist(userId); - } - /** * Adds or removes a requirement for given UID ranges to use the VPN. * @@ -3166,16 +3114,6 @@ public class ConnectivityManager { } } - /** - * Calls VpnManager#updateLockdownVpn. - * @deprecated TODO: remove when callers have migrated to VpnManager. - * @hide - */ - @Deprecated - public boolean updateLockdownVpn() { - return getVpnManager().updateLockdownVpn(); - } - /** * Set sign in error notification to visible or invisible * @@ -4537,8 +4475,6 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); - // TODO: Migrate callers to VpnManager#factoryReset. - getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4832,15 +4768,6 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } - /** - * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a - * private final mVpnManager because ConnectivityManager is initialized before VpnManager. - * @hide TODO: remove. - */ - public VpnManager getVpnManager() { - return mContext.getSystemService(VpnManager.class); - } - /** @hide */ public ConnectivityDiagnosticsManager createDiagnosticsManager() { return new ConnectivityDiagnosticsManager(mContext, mService); From 4aad90f1b15fe2b34c93e5753fd362154e6faae9 Mon Sep 17 00:00:00 2001 From: paulhu Date: Tue, 2 Feb 2021 22:31:30 +0800 Subject: [PATCH 049/232] Use alternative PhoneStateListener formal API MultinetworkPolicyTracker is part of Connectivity mainline module which cannot call @hide API to register PhoneStateListener. Thus, replace it to formal API. Bug: 171183530 Test: atest FrameworksNetTests Change-Id: Ib02790623e82726aaada33f559226020d1e0019b --- .../net/util/MultinetworkPolicyTracker.java | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java index 85e3fa3048..43fffd733e 100644 --- a/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -40,6 +40,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; /** * A class to encapsulate management of the "Smart Networking" capability of @@ -73,6 +75,32 @@ public class MultinetworkPolicyTracker { private volatile int mMeteredMultipathPreference; private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + // Mainline module can't use internal HandlerExecutor, so add an identical executor here. + private static class HandlerExecutor implements Executor { + @NonNull + private final Handler mHandler; + + HandlerExecutor(@NonNull Handler handler) { + mHandler = handler; + } + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } + } + } + + @VisibleForTesting + protected class ActiveDataSubscriptionIdChangedListener extends PhoneStateListener + implements PhoneStateListener.ActiveDataSubscriptionIdChangedListener { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveSubId = subId; + reevaluateInternal(); + } + } + public MultinetworkPolicyTracker(Context ctx, Handler handler) { this(ctx, handler, null); } @@ -93,14 +121,8 @@ public class MultinetworkPolicyTracker { } }; - ctx.getSystemService(TelephonyManager.class).listen( - new PhoneStateListener(handler.getLooper()) { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - mActiveSubId = subId; - reevaluateInternal(); - } - }, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + ctx.getSystemService(TelephonyManager.class).registerPhoneStateListener( + new HandlerExecutor(handler), new ActiveDataSubscriptionIdChangedListener()); updateAvoidBadWifi(); updateMeteredMultipathPreference(); From bb0277e7e60639e4b015cc008f790c6850b772e6 Mon Sep 17 00:00:00 2001 From: paulhu Date: Mon, 22 Feb 2021 15:40:43 +0800 Subject: [PATCH 050/232] Implement Settings#checkAndNoteChangeNetworkStateOperation on CS Connectivity is becoming a mainline module in S and ConnectivityManager#enforceChangePermission is using Settings#checkAndNoteChangeNetworkStateOperation for performing a strict and comprehensive check of whether a calling package is allowed to change the state of network. However, Mainline modules are not allowed to use non-formal APIs, fortunately CS is the only caller of this ConnectivityManager#enforceChangePermission. Thus, implement the Settings API on ConnectivityService and remove the ConnectivityManager#enforceChangePermission and Settings#checkAndNoteChangeNetworkStateOperation. Bug: 178565313 Test: atest FrameworksNetTests Change-Id: I6f03398c1735b89470ad5bdbe3a036929daeb53c Merged-In: I6f03398c1735b89470ad5bdbe3a036929daeb53c --- .../src/android/net/ConnectivityManager.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 6273f4bb57..66e7da43cb 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -2245,31 +2245,6 @@ public class ConnectivityManager { } } - /* TODO: These permissions checks don't belong in client-side code. Move them to - * services.jar, possibly in com.android.server.net. */ - - /** {@hide} */ - public static final void enforceChangePermission(Context context, - String callingPkg, String callingAttributionTag) { - int uid = Binder.getCallingUid(); - checkAndNoteChangeNetworkStateOperation(context, uid, callingPkg, - callingAttributionTag, true /* throwException */); - } - - /** - * Check if the package is a allowed to change the network state. This also accounts that such - * an access happened. - * - * @return {@code true} iff the package is allowed to change the network state. - */ - // TODO: Remove method and replace with direct call once R code is pushed to AOSP - private static boolean checkAndNoteChangeNetworkStateOperation(@NonNull Context context, - int uid, @NonNull String callingPackage, @Nullable String callingAttributionTag, - boolean throwException) { - return Settings.checkAndNoteChangeNetworkStateOperation(context, uid, callingPackage, - throwException); - } - /** * Check if the package is a allowed to write settings. This also accounts that such an access * happened. From 5b1411c6366861984b66c0decd862ef57585cd34 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 24 Feb 2021 14:45:39 +0900 Subject: [PATCH 051/232] Add public alternative for protectFromVpn The method is called directly by VpnService#protect. Bug: 171540887 Test: m Change-Id: I7cbb1ef1301dcf8d27b2cc39c0431a0156fe3442 --- framework/src/android/net/NetworkUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java index 9ccb04a44a..b5e8a614b8 100644 --- a/framework/src/android/net/NetworkUtils.java +++ b/framework/src/android/net/NetworkUtils.java @@ -91,7 +91,8 @@ public class NetworkUtils { * this socket will go directly to the underlying network, so its traffic will not be * forwarded through the VPN. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553, + publicAlternatives = "Use {@link android.net.VpnService#protect} instead.") public static native boolean protectFromVpn(FileDescriptor fd); /** From 5f658cbb9b808f097017ca1b98e6075e1cf475ad Mon Sep 17 00:00:00 2001 From: junyulai Date: Thu, 25 Feb 2021 09:12:04 +0800 Subject: [PATCH 052/232] Fix copyright license of VpnTransportInfo Test: TH Bug: None Change-Id: Id1fe2c62a0fd60b9833d8b00d2fa2ddc2f031004 --- framework/src/android/net/VpnTransportInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java index 0242ba0874..340141b78a 100644 --- a/framework/src/android/net/VpnTransportInfo.java +++ b/framework/src/android/net/VpnTransportInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. From f15fc7f9a80f9214e6656d8dbdd01559348a2adf Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 28 Jan 2021 13:37:03 +0900 Subject: [PATCH 053/232] Split out the connectivity API surface Split out connectivity APIs to connectivity module directories. This prepares future move of the connectivity code into a mainline module, but still keeps it implemented by framework-minus-apex for now: the API stubs are moved to framework-connectivity.stubs, but the implementation on device remains in the same place. This allows moving the connectivity code in/out of APEX with minimal changes. BYPASS_INCLUSIVE_LANGUAGE_REASON=Moving files, can't modify released API Bug: 171540887 Test: device boots, connectivity working Merged-In: I21c42f032efa6c10e36c749df3183ce9679303a7 (cherry-pick from internal branch with API files conflicts) Change-Id: I21c42f032efa6c10e36c749df3183ce9679303a7 --- framework/Android.bp | 25 ++ framework/api/current.txt | 469 +++++++++++++++++++++++++ framework/api/lint-baseline.txt | 4 + framework/api/module-lib-current.txt | 66 ++++ framework/api/module-lib-removed.txt | 1 + framework/api/removed.txt | 11 + framework/api/system-current.txt | 407 +++++++++++++++++++++ framework/api/system-lint-baseline.txt | 1 + framework/api/system-removed.txt | 1 + 9 files changed, 985 insertions(+) create mode 100644 framework/api/current.txt create mode 100644 framework/api/lint-baseline.txt create mode 100644 framework/api/module-lib-current.txt create mode 100644 framework/api/module-lib-removed.txt create mode 100644 framework/api/removed.txt create mode 100644 framework/api/system-current.txt create mode 100644 framework/api/system-lint-baseline.txt create mode 100644 framework/api/system-removed.txt diff --git a/framework/Android.bp b/framework/Android.bp index ffca971430..9da27d2711 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -59,3 +59,28 @@ filegroup { "//packages/modules/Connectivity:__subpackages__", ], } + +java_sdk_library { + name: "framework-connectivity", + api_only: true, + defaults: ["framework-module-defaults"], + // TODO: build against module API + platform_apis: true, + srcs: [ + ":framework-connectivity-sources", + ], + aidl: { + include_dirs: [ + // Include directories for parcelables that are part of the stable API, and need a + // one-line "parcelable X" .aidl declaration to be used in AIDL interfaces. + // TODO(b/180293679): remove these dependencies as they should not be necessary once + // the module builds against API (the parcelable declarations exist in framework.aidl) + "frameworks/base/core/java", // For framework parcelables + "frameworks/native/aidl/binder", // For PersistableBundle.aidl + ], + }, + libs: [ + "unsupportedappusage", + ], + permitted_packages: ["android.net", "com.android.connectivity.aidl"], +} diff --git a/framework/api/current.txt b/framework/api/current.txt new file mode 100644 index 0000000000..d4262a807a --- /dev/null +++ b/framework/api/current.txt @@ -0,0 +1,469 @@ +// Signature format: 2.0 +package android.net { + + public class CaptivePortal implements android.os.Parcelable { + method public int describeContents(); + method public void ignoreNetwork(); + method public void reportCaptivePortalDismissed(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public class ConnectivityDiagnosticsManager { + method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); + method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); + } + + public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { + ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback(); + method public void onConnectivityReportAvailable(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport); + method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport); + method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean); + } + + public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable { + ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); + method public int describeContents(); + method @NonNull public android.os.PersistableBundle getAdditionalInfo(); + method @NonNull public android.net.LinkProperties getLinkProperties(); + method @NonNull public android.net.Network getNetwork(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public long getReportTimestamp(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted"; + field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; + field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; + field public static final int NETWORK_PROBE_DNS = 4; // 0x4 + field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20 + field public static final int NETWORK_PROBE_HTTP = 8; // 0x8 + field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10 + field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40 + field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0 + field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2 + field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3 + field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1 + } + + public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable { + ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); + method public int describeContents(); + method public int getDetectionMethod(); + method @NonNull public android.net.LinkProperties getLinkProperties(); + method @NonNull public android.net.Network getNetwork(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public long getReportTimestamp(); + method @NonNull public android.os.PersistableBundle getStallDetails(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 + field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 + field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; + field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis"; + field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; + } + + public class ConnectivityManager { + method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); + method public boolean bindProcessToNetwork(@Nullable android.net.Network); + method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork(); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks(); + method @Deprecated public boolean getBackgroundDataSetting(); + method @Nullable public android.net.Network getBoundNetworkForProcess(); + method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress); + method @Nullable public android.net.ProxyInfo getDefaultProxy(); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference(); + method @Nullable public byte[] getNetworkWatchlistConfigHash(); + method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork(); + method public int getRestrictBackgroundStatus(); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); + method public boolean isDefaultNetworkActive(); + method @Deprecated public static boolean isNetworkTypeValid(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); + method public void releaseNetworkRequest(@NonNull android.app.PendingIntent); + method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener); + method @Deprecated public void reportBadNetwork(@Nullable android.net.Network); + method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean); + method public boolean requestBandwidthUpdate(@NonNull android.net.Network); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); + method @Deprecated public void setNetworkPreference(int); + method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network); + method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); + method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent); + field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; + field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 + field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; + field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; + field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo"; + field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover"; + field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; + field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; + field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; + field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType"; + field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; + field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; + field public static final String EXTRA_REASON = "reason"; + field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1 + field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4 + field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2 + field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1 + field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3 + field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2 + field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7 + field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8 + field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9 + field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0 + field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4 + field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5 + field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2 + field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3 + field @Deprecated public static final int TYPE_VPN = 17; // 0x11 + field @Deprecated public static final int TYPE_WIFI = 1; // 0x1 + field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6 + } + + public static class ConnectivityManager.NetworkCallback { + ctor public ConnectivityManager.NetworkCallback(); + method public void onAvailable(@NonNull android.net.Network); + method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean); + method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities); + method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties); + method public void onLosing(@NonNull android.net.Network, int); + method public void onLost(@NonNull android.net.Network); + method public void onUnavailable(); + } + + public static interface ConnectivityManager.OnNetworkActiveListener { + method public void onNetworkActive(); + } + + public class DhcpInfo implements android.os.Parcelable { + ctor public DhcpInfo(); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public int dns1; + field public int dns2; + field public int gateway; + field public int ipAddress; + field public int leaseDuration; + field public int netmask; + field public int serverAddress; + } + + public final class DnsResolver { + method @NonNull public static android.net.DnsResolver getInstance(); + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback>); + method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback>); + method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback); + method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback); + field public static final int CLASS_IN = 1; // 0x1 + field public static final int ERROR_PARSE = 0; // 0x0 + field public static final int ERROR_SYSTEM = 1; // 0x1 + field public static final int FLAG_EMPTY = 0; // 0x0 + field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 + field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 + field public static final int FLAG_NO_RETRY = 1; // 0x1 + field public static final int TYPE_A = 1; // 0x1 + field public static final int TYPE_AAAA = 28; // 0x1c + } + + public static interface DnsResolver.Callback { + method public void onAnswer(@NonNull T, int); + method public void onError(@NonNull android.net.DnsResolver.DnsException); + } + + public static class DnsResolver.DnsException extends java.lang.Exception { + field public final int code; + } + + public class InetAddresses { + method public static boolean isNumericAddress(@NonNull String); + method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String); + } + + public final class IpPrefix implements android.os.Parcelable { + method public boolean contains(@NonNull java.net.InetAddress); + method public int describeContents(); + method @NonNull public java.net.InetAddress getAddress(); + method @IntRange(from=0, to=128) public int getPrefixLength(); + method @NonNull public byte[] getRawAddress(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public class LinkAddress implements android.os.Parcelable { + method public int describeContents(); + method public java.net.InetAddress getAddress(); + method public int getFlags(); + method @IntRange(from=0, to=128) public int getPrefixLength(); + method public int getScope(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public final class LinkProperties implements android.os.Parcelable { + ctor public LinkProperties(); + method public boolean addRoute(@NonNull android.net.RouteInfo); + method public void clear(); + method public int describeContents(); + method @Nullable public java.net.Inet4Address getDhcpServerAddress(); + method @NonNull public java.util.List getDnsServers(); + method @Nullable public String getDomains(); + method @Nullable public android.net.ProxyInfo getHttpProxy(); + method @Nullable public String getInterfaceName(); + method @NonNull public java.util.List getLinkAddresses(); + method public int getMtu(); + method @Nullable public android.net.IpPrefix getNat64Prefix(); + method @Nullable public String getPrivateDnsServerName(); + method @NonNull public java.util.List getRoutes(); + method public boolean isPrivateDnsActive(); + method public boolean isWakeOnLanSupported(); + method public void setDhcpServerAddress(@Nullable java.net.Inet4Address); + method public void setDnsServers(@NonNull java.util.Collection); + method public void setDomains(@Nullable String); + method public void setHttpProxy(@Nullable android.net.ProxyInfo); + method public void setInterfaceName(@Nullable String); + method public void setLinkAddresses(@NonNull java.util.Collection); + method public void setMtu(int); + method public void setNat64Prefix(@Nullable android.net.IpPrefix); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public final class MacAddress implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]); + method @NonNull public static android.net.MacAddress fromString(@NonNull String); + method public int getAddressType(); + method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac(); + method public boolean isLocallyAssigned(); + method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress); + method @NonNull public byte[] toByteArray(); + method @NonNull public String toOuiString(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.net.MacAddress BROADCAST_ADDRESS; + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int TYPE_BROADCAST = 3; // 0x3 + field public static final int TYPE_MULTICAST = 2; // 0x2 + field public static final int TYPE_UNICAST = 1; // 0x1 + } + + public class Network implements android.os.Parcelable { + method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException; + method public void bindSocket(java.net.Socket) throws java.io.IOException; + method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException; + method public int describeContents(); + method public static android.net.Network fromNetworkHandle(long); + method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException; + method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException; + method public long getNetworkHandle(); + method public javax.net.SocketFactory getSocketFactory(); + method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException; + method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException; + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public final class NetworkCapabilities implements android.os.Parcelable { + ctor public NetworkCapabilities(); + ctor public NetworkCapabilities(android.net.NetworkCapabilities); + method public int describeContents(); + method public int getLinkDownstreamBandwidthKbps(); + method public int getLinkUpstreamBandwidthKbps(); + method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); + method public int getOwnerUid(); + method public int getSignalStrength(); + method @Nullable public android.net.TransportInfo getTransportInfo(); + method public boolean hasCapability(int); + method public boolean hasTransport(int); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11 + field public static final int NET_CAPABILITY_CBS = 5; // 0x5 + field public static final int NET_CAPABILITY_DUN = 2; // 0x2 + field public static final int NET_CAPABILITY_EIMS = 10; // 0xa + field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13 + field public static final int NET_CAPABILITY_FOTA = 3; // 0x3 + field public static final int NET_CAPABILITY_IA = 7; // 0x7 + field public static final int NET_CAPABILITY_IMS = 4; // 0x4 + field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc + field public static final int NET_CAPABILITY_MCX = 23; // 0x17 + field public static final int NET_CAPABILITY_MMS = 0; // 0x0 + field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14 + field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb + field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 + field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15 + field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf + field public static final int NET_CAPABILITY_RCS = 8; // 0x8 + field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 + field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19 + field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe + field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10 + field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6 + field public static final int NET_CAPABILITY_XCAP = 9; // 0x9 + field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000 + field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 + field public static final int TRANSPORT_CELLULAR = 0; // 0x0 + field public static final int TRANSPORT_ETHERNET = 3; // 0x3 + field public static final int TRANSPORT_LOWPAN = 6; // 0x6 + field public static final int TRANSPORT_VPN = 4; // 0x4 + field public static final int TRANSPORT_WIFI = 1; // 0x1 + field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5 + } + + @Deprecated public class NetworkInfo implements android.os.Parcelable { + ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String); + method @Deprecated public int describeContents(); + method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState(); + method @Deprecated public String getExtraInfo(); + method @Deprecated public String getReason(); + method @Deprecated public android.net.NetworkInfo.State getState(); + method @Deprecated public int getSubtype(); + method @Deprecated public String getSubtypeName(); + method @Deprecated public int getType(); + method @Deprecated public String getTypeName(); + method @Deprecated public boolean isAvailable(); + method @Deprecated public boolean isConnected(); + method @Deprecated public boolean isConnectedOrConnecting(); + method @Deprecated public boolean isFailover(); + method @Deprecated public boolean isRoaming(); + method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + @Deprecated public enum NetworkInfo.DetailedState { + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK; + } + + @Deprecated public enum NetworkInfo.State { + enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN; + } + + public class NetworkRequest implements android.os.Parcelable { + method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities); + method public int describeContents(); + method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); + method public boolean hasCapability(int); + method public boolean hasTransport(int); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static class NetworkRequest.Builder { + ctor public NetworkRequest.Builder(); + method public android.net.NetworkRequest.Builder addCapability(int); + method public android.net.NetworkRequest.Builder addTransportType(int); + method public android.net.NetworkRequest build(); + method @NonNull public android.net.NetworkRequest.Builder clearCapabilities(); + method public android.net.NetworkRequest.Builder removeCapability(int); + method public android.net.NetworkRequest.Builder removeTransportType(int); + method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); + method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); + } + + public final class Proxy { + ctor public Proxy(); + method @Deprecated public static String getDefaultHost(); + method @Deprecated public static int getDefaultPort(); + method @Deprecated public static String getHost(android.content.Context); + method @Deprecated public static int getPort(android.content.Context); + field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; + field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; + } + + public class ProxyInfo implements android.os.Parcelable { + ctor public ProxyInfo(@Nullable android.net.ProxyInfo); + method public static android.net.ProxyInfo buildDirectProxy(String, int); + method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List); + method public static android.net.ProxyInfo buildPacProxy(android.net.Uri); + method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int); + method public int describeContents(); + method public String[] getExclusionList(); + method public String getHost(); + method public android.net.Uri getPacFileUrl(); + method public int getPort(); + method public boolean isValid(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public final class RouteInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.net.IpPrefix getDestination(); + method @Nullable public java.net.InetAddress getGateway(); + method @Nullable public String getInterface(); + method public boolean hasGateway(); + method public boolean isDefaultRoute(); + method public boolean matches(java.net.InetAddress); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + method public final void close(); + method public final void start(@IntRange(from=0xa, to=0xe10) int); + method public final void stop(); + field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 + field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0 + field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 + field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 + field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2 + } + + public static class SocketKeepalive.Callback { + ctor public SocketKeepalive.Callback(); + method public void onDataReceived(); + method public void onError(int); + method public void onStarted(); + method public void onStopped(); + } + + public interface TransportInfo { + } + +} + diff --git a/framework/api/lint-baseline.txt b/framework/api/lint-baseline.txt new file mode 100644 index 0000000000..2f4004ab72 --- /dev/null +++ b/framework/api/lint-baseline.txt @@ -0,0 +1,4 @@ +// Baseline format: 1.0 +VisiblySynchronized: android.net.NetworkInfo#toString(): + Internal locks must not be exposed (synchronizing on this or class is still + externally observable): method android.net.NetworkInfo.toString() diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt new file mode 100644 index 0000000000..3af855ec1e --- /dev/null +++ b/framework/api/module-lib-current.txt @@ -0,0 +1,66 @@ +// Signature format: 2.0 +package android.net { + + public final class ConnectivityFrameworkInitializer { + method public static void registerServiceWrappers(); + } + + public class ConnectivityManager { + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); + } + + public final class NetworkAgentConfig implements android.os.Parcelable { + method @Nullable public String getSubscriberId(); + } + + public static final class NetworkAgentConfig.Builder { + method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); + } + + public final class NetworkCapabilities implements android.os.Parcelable { + field public static final int TRANSPORT_TEST = 7; // 0x7 + } + + public final class Proxy { + method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); + } + + public final class TcpRepairWindow { + ctor public TcpRepairWindow(int, int, int, int, int, int); + field public final int maxWindow; + field public final int rcvWnd; + field public final int rcvWndScale; + field public final int rcvWup; + field public final int sndWl1; + field public final int sndWnd; + } + + public final class TestNetworkInterface implements android.os.Parcelable { + ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String); + method public int describeContents(); + method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor(); + method @NonNull public String getInterfaceName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public class TestNetworkManager { + method @NonNull public android.net.TestNetworkInterface createTapInterface(); + method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection); + method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); + method public void teardownTestNetwork(@NonNull android.net.Network); + field public static final String TEST_TAP_PREFIX = "testtap"; + } + + public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { + ctor public VpnTransportInfo(int); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public final int type; + } + +} + diff --git a/framework/api/module-lib-removed.txt b/framework/api/module-lib-removed.txt new file mode 100644 index 0000000000..d802177e24 --- /dev/null +++ b/framework/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/framework/api/removed.txt b/framework/api/removed.txt new file mode 100644 index 0000000000..303a1e6173 --- /dev/null +++ b/framework/api/removed.txt @@ -0,0 +1,11 @@ +// Signature format: 2.0 +package android.net { + + public class ConnectivityManager { + method @Deprecated public boolean requestRouteToHost(int, int); + method @Deprecated public int startUsingNetworkFeature(int, String); + method @Deprecated public int stopUsingNetworkFeature(int, String); + } + +} + diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt new file mode 100644 index 0000000000..41ebc5774f --- /dev/null +++ b/framework/api/system-current.txt @@ -0,0 +1,407 @@ +// Signature format: 2.0 +package android.net { + + public class CaptivePortal implements android.os.Parcelable { + method public void logEvent(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); + method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 + field public static final int APP_RETURN_DISMISSED = 0; // 0x0 + field public static final int APP_RETURN_UNWANTED = 1; // 0x1 + field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 + } + + public final class CaptivePortalData implements android.os.Parcelable { + method public int describeContents(); + method public long getByteLimit(); + method public long getExpiryTimeMillis(); + method public long getRefreshTimeMillis(); + method @Nullable public android.net.Uri getUserPortalUrl(); + method public int getUserPortalUrlSource(); + method @Nullable public String getVenueFriendlyName(); + method @Nullable public android.net.Uri getVenueInfoUrl(); + method public int getVenueInfoUrlSource(); + method public boolean isCaptive(); + method public boolean isSessionExtendable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0 + field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1 + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static class CaptivePortalData.Builder { + ctor public CaptivePortalData.Builder(); + ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData); + method @NonNull public android.net.CaptivePortalData build(); + method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long); + method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int); + method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int); + } + + public class ConnectivityManager { + method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); + method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); + method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); + method public void unregisterQosCallback(@NonNull android.net.QosCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); + field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; + field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + field public static final int TETHERING_BLUETOOTH = 2; // 0x2 + field public static final int TETHERING_USB = 1; // 0x1 + field public static final int TETHERING_WIFI = 0; // 0x0 + field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd + field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field public static final int TYPE_NONE = -1; // 0xffffffff + field @Deprecated public static final int TYPE_PROXY = 16; // 0x10 + field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd + } + + public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { + method public void onComplete(); + } + + @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { + ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); + method @Deprecated public void onTetheringFailed(); + method @Deprecated public void onTetheringStarted(); + } + + @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener { + method @Deprecated public void onTetheringEntitlementResult(int); + } + + @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback { + ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback(); + method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network); + } + + public final class InvalidPacketException extends java.lang.Exception { + ctor public InvalidPacketException(int); + method public int getError(); + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + } + + public final class IpConfiguration implements android.os.Parcelable { + ctor public IpConfiguration(); + ctor public IpConfiguration(@NonNull android.net.IpConfiguration); + method public int describeContents(); + method @Nullable public android.net.ProxyInfo getHttpProxy(); + method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment(); + method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings(); + method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration(); + method public void setHttpProxy(@Nullable android.net.ProxyInfo); + method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment); + method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings); + method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public enum IpConfiguration.IpAssignment { + enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP; + enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC; + enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED; + } + + public enum IpConfiguration.ProxySettings { + enum_constant public static final android.net.IpConfiguration.ProxySettings NONE; + enum_constant public static final android.net.IpConfiguration.ProxySettings PAC; + enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC; + enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED; + } + + public final class IpPrefix implements android.os.Parcelable { + ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); + ctor public IpPrefix(@NonNull String); + } + + public class KeepalivePacketData { + ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException; + method @NonNull public java.net.InetAddress getDstAddress(); + method public int getDstPort(); + method @NonNull public byte[] getPacket(); + method @NonNull public java.net.InetAddress getSrcAddress(); + method public int getSrcPort(); + } + + public class LinkAddress implements android.os.Parcelable { + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); + ctor public LinkAddress(@NonNull String); + ctor public LinkAddress(@NonNull String, int, int); + method public long getDeprecationTime(); + method public long getExpirationTime(); + method public boolean isGlobalPreferred(); + method public boolean isIpv4(); + method public boolean isIpv6(); + method public boolean isSameAddressAs(@Nullable android.net.LinkAddress); + field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL + field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL + } + + public final class LinkProperties implements android.os.Parcelable { + ctor public LinkProperties(@Nullable android.net.LinkProperties); + ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean); + method public boolean addDnsServer(@NonNull java.net.InetAddress); + method public boolean addLinkAddress(@NonNull android.net.LinkAddress); + method public boolean addPcscfServer(@NonNull java.net.InetAddress); + method @NonNull public java.util.List getAddresses(); + method @NonNull public java.util.List getAllInterfaceNames(); + method @NonNull public java.util.List getAllLinkAddresses(); + method @NonNull public java.util.List getAllRoutes(); + method @Nullable public android.net.Uri getCaptivePortalApiUrl(); + method @Nullable public android.net.CaptivePortalData getCaptivePortalData(); + method @NonNull public java.util.List getPcscfServers(); + method @Nullable public String getTcpBufferSizes(); + method @NonNull public java.util.List getValidatedPrivateDnsServers(); + method public boolean hasGlobalIpv6Address(); + method public boolean hasIpv4Address(); + method public boolean hasIpv4DefaultRoute(); + method public boolean hasIpv4DnsServer(); + method public boolean hasIpv6DefaultRoute(); + method public boolean hasIpv6DnsServer(); + method public boolean isIpv4Provisioned(); + method public boolean isIpv6Provisioned(); + method public boolean isProvisioned(); + method public boolean isReachable(@NonNull java.net.InetAddress); + method public boolean removeDnsServer(@NonNull java.net.InetAddress); + method public boolean removeLinkAddress(@NonNull android.net.LinkAddress); + method public boolean removeRoute(@NonNull android.net.RouteInfo); + method public void setCaptivePortalApiUrl(@Nullable android.net.Uri); + method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData); + method public void setPcscfServers(@NonNull java.util.Collection); + method public void setPrivateDnsServerName(@Nullable String); + method public void setTcpBufferSizes(@Nullable String); + method public void setUsePrivateDns(boolean); + method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection); + } + + public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public class Network implements android.os.Parcelable { + ctor public Network(@NonNull android.net.Network); + method public int getNetId(); + method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); + } + + public abstract class NetworkAgent { + ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); + method @Nullable public android.net.Network getNetwork(); + method public void markConnected(); + method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); + method public void onAutomaticReconnectDisabled(); + method public void onNetworkUnwanted(); + method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); + method public void onQosCallbackUnregistered(int); + method public void onRemoveKeepalivePacketFilter(int); + method public void onSaveAcceptUnvalidated(boolean); + method public void onSignalStrengthThresholdsUpdated(@NonNull int[]); + method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData); + method public void onStopSocketKeepalive(int); + method public void onValidationStatus(int, @Nullable android.net.Uri); + method @NonNull public android.net.Network register(); + method public final void sendLinkProperties(@NonNull android.net.LinkProperties); + method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); + method public final void sendNetworkScore(@IntRange(from=0, to=99) int); + method public final void sendQosCallbackError(int, int); + method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); + method public final void sendQosSessionLost(int, int); + method public final void sendSocketKeepaliveEvent(int, int); + method public final void setUnderlyingNetworks(@Nullable java.util.List); + method public void unregister(); + field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 + field public static final int VALIDATION_STATUS_VALID = 1; // 0x1 + } + + public final class NetworkAgentConfig implements android.os.Parcelable { + method public int describeContents(); + method public int getLegacyType(); + method @NonNull public String getLegacyTypeName(); + method public boolean isExplicitlySelected(); + method public boolean isPartialConnectivityAcceptable(); + method public boolean isUnvalidatedConnectivityAcceptable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class NetworkAgentConfig.Builder { + ctor public NetworkAgentConfig.Builder(); + method @NonNull public android.net.NetworkAgentConfig build(); + method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); + method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); + method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); + } + + public final class NetworkCapabilities implements android.os.Parcelable { + ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean); + method @NonNull public int[] getAdministratorUids(); + method @Nullable public String getSsid(); + method @NonNull public int[] getTransportTypes(); + method public boolean isPrivateDnsBroken(); + method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); + field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c + field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 + field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a + field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 + field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b + } + + public static final class NetworkCapabilities.Builder { + ctor public NetworkCapabilities.Builder(); + ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); + method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); + method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int); + method @NonNull public android.net.NetworkCapabilities build(); + method @NonNull public android.net.NetworkCapabilities.Builder clearAll(); + method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int); + method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]); + method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int); + method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int); + method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); + method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); + } + + public class NetworkProvider { + ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest); + method public int getProviderId(); + method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest); + method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int); + field public static final int ID_NONE = -1; // 0xffffffff + } + + public class NetworkRequest implements android.os.Parcelable { + method @Nullable public String getRequestorPackageName(); + method public int getRequestorUid(); + } + + public static class NetworkRequest.Builder { + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); + } + + public final class RouteInfo implements android.os.Parcelable { + ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); + ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); + method public int getMtu(); + method public int getType(); + field public static final int RTN_THROW = 9; // 0x9 + field public static final int RTN_UNICAST = 1; // 0x1 + field public static final int RTN_UNREACHABLE = 7; // 0x7 + } + + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + field public static final int SUCCESS = 0; // 0x0 + } + + public final class StaticIpConfiguration implements android.os.Parcelable { + ctor public StaticIpConfiguration(); + ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); + method public void addDnsServer(@NonNull java.net.InetAddress); + method public void clear(); + method public int describeContents(); + method @NonNull public java.util.List getDnsServers(); + method @Nullable public String getDomains(); + method @Nullable public java.net.InetAddress getGateway(); + method @Nullable public android.net.LinkAddress getIpAddress(); + method @NonNull public java.util.List getRoutes(@Nullable String); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class StaticIpConfiguration.Builder { + ctor public StaticIpConfiguration.Builder(); + method @NonNull public android.net.StaticIpConfiguration build(); + method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable); + method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String); + method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress); + method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress); + } + + public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public final int ipTos; + field public final int ipTtl; + field public final int tcpAck; + field public final int tcpSeq; + field public final int tcpWindow; + field public final int tcpWindowScale; + } + + public interface TransportInfo { + method public default boolean hasLocationSensitiveFields(); + method @NonNull public default android.net.TransportInfo makeCopy(boolean); + } + +} + +package android.net.apf { + + public final class ApfCapabilities implements android.os.Parcelable { + ctor public ApfCapabilities(int, int, int); + method public int describeContents(); + method public static boolean getApfDrop8023Frames(); + method @NonNull public static int[] getApfEtherTypeBlackList(); + method public boolean hasDataAccess(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public final int apfPacketFormat; + field public final int apfVersionSupported; + field public final int maximumApfProgramSize; + } + +} + +package android.net.util { + + public final class SocketUtils { + method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException; + method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException; + method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); + method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int); + method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]); + method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]); + } + +} + diff --git a/framework/api/system-lint-baseline.txt b/framework/api/system-lint-baseline.txt new file mode 100644 index 0000000000..9a97707763 --- /dev/null +++ b/framework/api/system-lint-baseline.txt @@ -0,0 +1 @@ +// Baseline format: 1.0 diff --git a/framework/api/system-removed.txt b/framework/api/system-removed.txt new file mode 100644 index 0000000000..d802177e24 --- /dev/null +++ b/framework/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 From 1e290800373818b96361d0fe6beb99f2966dcf83 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 18 Feb 2021 00:03:53 +0900 Subject: [PATCH 054/232] Move SocketUtils out of the connectivity module SocketUtils contains system APIs for modules to interact for sockets, wrapping internal APIs. It should be part of the platform to keep access to the internal APIs. This involves splitting NetworkUtils.protectVpn to NetworkUtilsInternal, since SocketUtils and VpnService are the only users of that method. The @UnsupportedAppUsage NetworkUtils.protectVpn has low usage count, and is already available through VpnService.protect. Bug: 181512874 Test: boots, VPN working Change-Id: I7028d334975f7536c06afac7a22200c33db707ac --- framework/api/system-current.txt | 13 -- framework/src/android/net/NetworkUtils.java | 16 --- .../src/android/net/util/SocketUtils.java | 121 ------------------ 3 files changed, 150 deletions(-) delete mode 100644 framework/src/android/net/util/SocketUtils.java diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 41ebc5774f..b541e5238f 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -392,16 +392,3 @@ package android.net.apf { } -package android.net.util { - - public final class SocketUtils { - method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException; - method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException; - method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); - method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int); - method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]); - method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]); - } - -} - diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java index b5e8a614b8..9e42bbecbe 100644 --- a/framework/src/android/net/NetworkUtils.java +++ b/framework/src/android/net/NetworkUtils.java @@ -86,22 +86,6 @@ public class NetworkUtils { */ public static native int bindSocketToNetwork(FileDescriptor fd, int netId); - /** - * Protect {@code fd} from VPN connections. After protecting, data sent through - * this socket will go directly to the underlying network, so its traffic will not be - * forwarded through the VPN. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553, - publicAlternatives = "Use {@link android.net.VpnService#protect} instead.") - public static native boolean protectFromVpn(FileDescriptor fd); - - /** - * Protect {@code socketfd} from VPN connections. After protecting, data sent through - * this socket will go directly to the underlying network, so its traffic will not be - * forwarded through the VPN. - */ - public native static boolean protectFromVpn(int socketfd); - /** * Determine if {@code uid} can access network designated by {@code netId}. * @return {@code true} if {@code uid} can access network, {@code false} otherwise. diff --git a/framework/src/android/net/util/SocketUtils.java b/framework/src/android/net/util/SocketUtils.java deleted file mode 100644 index e64060f1b2..0000000000 --- a/framework/src/android/net/util/SocketUtils.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2015 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 static android.system.OsConstants.SOL_SOCKET; -import static android.system.OsConstants.SO_BINDTODEVICE; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.net.NetworkUtils; -import android.system.ErrnoException; -import android.system.NetlinkSocketAddress; -import android.system.Os; -import android.system.PacketSocketAddress; - -import libcore.io.IoBridge; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.SocketAddress; - -/** - * Collection of utilities to interact with raw sockets. - * @hide - */ -@SystemApi -public final class SocketUtils { - /** - * Create a raw datagram socket that is bound to an interface. - * - *

Data sent through the socket will go directly to the underlying network, ignoring VPNs. - */ - public static void bindSocketToInterface(@NonNull FileDescriptor socket, @NonNull String iface) - throws ErrnoException { - // SO_BINDTODEVICE actually takes a string. This works because the first member - // of struct ifreq is a NULL-terminated interface name. - // TODO: add a setsockoptString() - Os.setsockoptIfreq(socket, SOL_SOCKET, SO_BINDTODEVICE, iface); - NetworkUtils.protectFromVpn(socket); - } - - /** - * Make a socket address to communicate with netlink. - */ - @NonNull - public static SocketAddress makeNetlinkSocketAddress(int portId, int groupsMask) { - return new NetlinkSocketAddress(portId, groupsMask); - } - - /** - * Make socket address that packet sockets can bind to. - * - * @param protocol the layer 2 protocol of the packets to receive. One of the {@code ETH_P_*} - * constants in {@link android.system.OsConstants}. - * @param ifIndex the interface index on which packets will be received. - */ - @NonNull - public static SocketAddress makePacketSocketAddress(int protocol, int ifIndex) { - return new PacketSocketAddress( - protocol /* sll_protocol */, - ifIndex /* sll_ifindex */, - null /* sll_addr */); - } - - /** - * Make a socket address that packet socket can send packets to. - * @deprecated Use {@link #makePacketSocketAddress(int, int, byte[])} instead. - * - * @param ifIndex the interface index on which packets will be sent. - * @param hwAddr the hardware address to which packets will be sent. - */ - @Deprecated - @NonNull - public static SocketAddress makePacketSocketAddress(int ifIndex, @NonNull byte[] hwAddr) { - return new PacketSocketAddress( - 0 /* sll_protocol */, - ifIndex /* sll_ifindex */, - hwAddr /* sll_addr */); - } - - /** - * Make a socket address that a packet socket can send packets to. - * - * @param protocol the layer 2 protocol of the packets to send. One of the {@code ETH_P_*} - * constants in {@link android.system.OsConstants}. - * @param ifIndex the interface index on which packets will be sent. - * @param hwAddr the hardware address to which packets will be sent. - */ - @NonNull - public static SocketAddress makePacketSocketAddress(int protocol, int ifIndex, - @NonNull byte[] hwAddr) { - return new PacketSocketAddress( - protocol /* sll_protocol */, - ifIndex /* sll_ifindex */, - hwAddr /* sll_addr */); - } - - /** - * @see IoBridge#closeAndSignalBlockedThreads(FileDescriptor) - */ - public static void closeSocket(@Nullable FileDescriptor fd) throws IOException { - IoBridge.closeAndSignalBlockedThreads(fd); - } - - private SocketUtils() {} -} From 070ff8bb9cd9931e69a3793081339a7121da7e33 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 1 Mar 2021 17:53:45 +0900 Subject: [PATCH 055/232] Move QoS API classes to Connectivity The classes were added in S and are used to back ConnectivityManager APIs. Add them to the connectivity boundary as they belong together with ConnectivityManager. Bug: 181512874 Test: m Change-Id: I03b5978949b200a72813d1ebc4812d851fe3df37 --- .../android/net/QosFilterParcelable.aidl | 21 +++ .../aidl-export/android/net/QosSession.aidl | 21 +++ .../android/net/QosSocketInfo.aidl | 21 +++ framework/api/system-current.txt | 50 ++++++ framework/src/android/net/IQosCallback.aidl | 34 ++++ .../android/net/NetworkReleasedException.java | 32 ++++ framework/src/android/net/QosCallback.java | 91 ++++++++++ .../android/net/QosCallbackConnection.java | 128 ++++++++++++++ .../src/android/net/QosCallbackException.java | 110 ++++++++++++ framework/src/android/net/QosFilter.java | 75 ++++++++ .../src/android/net/QosFilterParcelable.java | 113 ++++++++++++ framework/src/android/net/QosSession.java | 136 ++++++++++++++ .../src/android/net/QosSessionAttributes.java | 30 ++++ .../src/android/net/QosSocketFilter.java | 166 ++++++++++++++++++ framework/src/android/net/QosSocketInfo.java | 154 ++++++++++++++++ .../SocketLocalAddressChangedException.java | 32 ++++ .../android/net/SocketNotBoundException.java | 32 ++++ 17 files changed, 1246 insertions(+) create mode 100644 framework/aidl-export/android/net/QosFilterParcelable.aidl create mode 100644 framework/aidl-export/android/net/QosSession.aidl create mode 100644 framework/aidl-export/android/net/QosSocketInfo.aidl create mode 100644 framework/src/android/net/IQosCallback.aidl create mode 100644 framework/src/android/net/NetworkReleasedException.java create mode 100644 framework/src/android/net/QosCallback.java create mode 100644 framework/src/android/net/QosCallbackConnection.java create mode 100644 framework/src/android/net/QosCallbackException.java create mode 100644 framework/src/android/net/QosFilter.java create mode 100644 framework/src/android/net/QosFilterParcelable.java create mode 100644 framework/src/android/net/QosSession.java create mode 100644 framework/src/android/net/QosSessionAttributes.java create mode 100644 framework/src/android/net/QosSocketFilter.java create mode 100644 framework/src/android/net/QosSocketInfo.java create mode 100644 framework/src/android/net/SocketLocalAddressChangedException.java create mode 100644 framework/src/android/net/SocketNotBoundException.java diff --git a/framework/aidl-export/android/net/QosFilterParcelable.aidl b/framework/aidl-export/android/net/QosFilterParcelable.aidl new file mode 100644 index 0000000000..312d6352ee --- /dev/null +++ b/framework/aidl-export/android/net/QosFilterParcelable.aidl @@ -0,0 +1,21 @@ +/* +** +** 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 android.net; + +parcelable QosFilterParcelable; + diff --git a/framework/aidl-export/android/net/QosSession.aidl b/framework/aidl-export/android/net/QosSession.aidl new file mode 100644 index 0000000000..c2cf36624b --- /dev/null +++ b/framework/aidl-export/android/net/QosSession.aidl @@ -0,0 +1,21 @@ +/* +** +** 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 android.net; + +parcelable QosSession; + diff --git a/framework/aidl-export/android/net/QosSocketInfo.aidl b/framework/aidl-export/android/net/QosSocketInfo.aidl new file mode 100644 index 0000000000..476c0900e2 --- /dev/null +++ b/framework/aidl-export/android/net/QosSocketInfo.aidl @@ -0,0 +1,21 @@ +/* +** +** 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 android.net; + +parcelable QosSocketInfo; + diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 41ebc5774f..40c2cd10ba 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -308,6 +308,9 @@ package android.net { field public static final int ID_NONE = -1; // 0xffffffff } + public class NetworkReleasedException extends java.lang.Exception { + } + public class NetworkRequest implements android.os.Parcelable { method @Nullable public String getRequestorPackageName(); method public int getRequestorUid(); @@ -317,6 +320,47 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); } + public abstract class QosCallback { + ctor public QosCallback(); + method public void onError(@NonNull android.net.QosCallbackException); + method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes); + method public void onQosSessionLost(@NonNull android.net.QosSession); + } + + public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException { + } + + public final class QosCallbackException extends java.lang.Exception { + } + + public abstract class QosFilter { + method @NonNull public abstract android.net.Network getNetwork(); + method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int); + } + + public final class QosSession implements android.os.Parcelable { + ctor public QosSession(int, int); + method public int describeContents(); + method public int getSessionId(); + method public int getSessionType(); + method public long getUniqueId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int TYPE_EPS_BEARER = 1; // 0x1 + } + + public interface QosSessionAttributes { + } + + public final class QosSocketInfo implements android.os.Parcelable { + ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException; + method public int describeContents(); + method @NonNull public java.net.InetSocketAddress getLocalSocketAddress(); + method @NonNull public android.net.Network getNetwork(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public final class RouteInfo implements android.os.Parcelable { ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); @@ -331,6 +375,12 @@ package android.net { field public static final int SUCCESS = 0; // 0x0 } + public class SocketLocalAddressChangedException extends java.lang.Exception { + } + + public class SocketNotBoundException extends java.lang.Exception { + } + public final class StaticIpConfiguration implements android.os.Parcelable { ctor public StaticIpConfiguration(); ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); diff --git a/framework/src/android/net/IQosCallback.aidl b/framework/src/android/net/IQosCallback.aidl new file mode 100644 index 0000000000..91c75759f8 --- /dev/null +++ b/framework/src/android/net/IQosCallback.aidl @@ -0,0 +1,34 @@ +/* + * 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 android.net; + +import android.os.Bundle; +import android.net.QosSession; +import android.telephony.data.EpsBearerQosSessionAttributes; + +/** + * AIDL interface for QosCallback + * + * @hide + */ +oneway interface IQosCallback +{ + void onQosEpsBearerSessionAvailable(in QosSession session, + in EpsBearerQosSessionAttributes attributes); + void onQosSessionLost(in QosSession session); + void onError(in int type); +} diff --git a/framework/src/android/net/NetworkReleasedException.java b/framework/src/android/net/NetworkReleasedException.java new file mode 100644 index 0000000000..0629b7563a --- /dev/null +++ b/framework/src/android/net/NetworkReleasedException.java @@ -0,0 +1,32 @@ +/* + * 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 android.net; + +import android.annotation.SystemApi; + +/** + * Indicates that the {@link Network} was released and is no longer available. + * + * @hide + */ +@SystemApi +public class NetworkReleasedException extends Exception { + /** @hide */ + public NetworkReleasedException() { + super("The network was released and is no longer available"); + } +} diff --git a/framework/src/android/net/QosCallback.java b/framework/src/android/net/QosCallback.java new file mode 100644 index 0000000000..22f06bc0e6 --- /dev/null +++ b/framework/src/android/net/QosCallback.java @@ -0,0 +1,91 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import java.util.concurrent.Executor; + +/** + * Receives Qos information given a {@link Network}. The callback is registered with + * {@link ConnectivityManager#registerQosCallback}. + * + *

+ *
+ * The callback will no longer receive calls if any of the following takes place: + *

    + *
  1. {@link ConnectivityManager#unregisterQosCallback(QosCallback)} is called with the same + * callback instance.
  2. + *
  3. {@link QosCallback#onError(QosCallbackException)} is called.
  4. + *
  5. A network specific issue occurs. eg. Congestion on a carrier network.
  6. + *
  7. The network registered with the callback has no associated QoS providers
  8. + * + * {@hide} + */ +@SystemApi +public abstract class QosCallback { + /** + * Invoked after an error occurs on a registered callback. Once called, the callback is + * automatically unregistered and the callback will no longer receive calls. + * + *

    The underlying exception can either be a runtime exception or a custom exception made for + * {@link QosCallback}. see: {@link QosCallbackException}. + * + * @param exception wraps the underlying cause + */ + public void onError(@NonNull final QosCallbackException exception) { + } + + /** + * Called when a Qos Session first becomes available to the callback or if its attributes have + * changed. + *

    + * Note: The callback may be called multiple times with the same attributes. + * + * @param session the available session + * @param sessionAttributes the attributes of the session + */ + public void onQosSessionAvailable(@NonNull final QosSession session, + @NonNull final QosSessionAttributes sessionAttributes) { + } + + /** + * Called after a Qos Session is lost. + *

    + * At least one call to + * {@link QosCallback#onQosSessionAvailable(QosSession, QosSessionAttributes)} + * with the same {@link QosSession} will precede a call to lost. + * + * @param session the lost session + */ + public void onQosSessionLost(@NonNull final QosSession session) { + } + + /** + * Thrown when there is a problem registering {@link QosCallback} with + * {@link ConnectivityManager#registerQosCallback(QosSocketInfo, QosCallback, Executor)}. + */ + public static class QosCallbackRegistrationException extends RuntimeException { + /** + * @hide + */ + public QosCallbackRegistrationException() { + super(); + } + } +} diff --git a/framework/src/android/net/QosCallbackConnection.java b/framework/src/android/net/QosCallbackConnection.java new file mode 100644 index 0000000000..bdb4ad68cd --- /dev/null +++ b/framework/src/android/net/QosCallbackConnection.java @@ -0,0 +1,128 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.telephony.data.EpsBearerQosSessionAttributes; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Sends messages from {@link com.android.server.ConnectivityService} to the registered + * {@link QosCallback}. + *

    + * This is a satellite class of {@link ConnectivityManager} and not meant + * to be used in other contexts. + * + * @hide + */ +class QosCallbackConnection extends android.net.IQosCallback.Stub { + + @NonNull private final ConnectivityManager mConnectivityManager; + @Nullable private volatile QosCallback mCallback; + @NonNull private final Executor mExecutor; + + @VisibleForTesting + @Nullable + public QosCallback getCallback() { + return mCallback; + } + + /** + * The constructor for the connection + * + * @param connectivityManager the mgr that created this connection + * @param callback the callback to send messages back to + * @param executor The executor on which the callback will be invoked. The provided + * {@link Executor} must run callback sequentially, otherwise the order of + * callbacks cannot be guaranteed. + */ + QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager, + @NonNull final QosCallback callback, + @NonNull final Executor executor) { + mConnectivityManager = Objects.requireNonNull(connectivityManager, + "connectivityManager must be non-null"); + mCallback = Objects.requireNonNull(callback, "callback must be non-null"); + mExecutor = Objects.requireNonNull(executor, "executor must be non-null"); + } + + /** + * Called when either the {@link EpsBearerQosSessionAttributes} has changed or on the first time + * the attributes have become available. + * + * @param session the session that is now available + * @param attributes the corresponding attributes of session + */ + @Override + public void onQosEpsBearerSessionAvailable(@NonNull final QosSession session, + @NonNull final EpsBearerQosSessionAttributes attributes) { + + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionAvailable(session, attributes); + } + }); + } + + /** + * Called when the session is lost. + * + * @param session the session that was lost + */ + @Override + public void onQosSessionLost(@NonNull final QosSession session) { + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionLost(session); + } + }); + } + + /** + * Called when there is an error on the registered callback. + * + * @param errorType the type of error + */ + @Override + public void onError(@QosCallbackException.ExceptionType final int errorType) { + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + // Messages no longer need to be received since there was an error. + stopReceivingMessages(); + mConnectivityManager.unregisterQosCallback(callback); + callback.onError(QosCallbackException.createException(errorType)); + } + }); + } + + /** + * The callback will stop receiving messages. + *

    + * There are no synchronization guarantees on exactly when the callback will stop receiving + * messages. + */ + void stopReceivingMessages() { + mCallback = null; + } +} diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java new file mode 100644 index 0000000000..7fd9a527e2 --- /dev/null +++ b/framework/src/android/net/QosCallbackException.java @@ -0,0 +1,110 @@ +/* + * 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 android.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This is the exception type passed back through the onError method on {@link QosCallback}. + * {@link QosCallbackException#getCause()} contains the actual error that caused this exception. + * + * The possible exception types as causes are: + * 1. {@link NetworkReleasedException} + * 2. {@link SocketNotBoundException} + * 3. {@link UnsupportedOperationException} + * 4. {@link SocketLocalAddressChangedException} + * + * @hide + */ +@SystemApi +public final class QosCallbackException extends Exception { + + /** @hide */ + @IntDef(prefix = {"EX_TYPE_"}, value = { + EX_TYPE_FILTER_NONE, + EX_TYPE_FILTER_NETWORK_RELEASED, + EX_TYPE_FILTER_SOCKET_NOT_BOUND, + EX_TYPE_FILTER_NOT_SUPPORTED, + EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ExceptionType {} + + private static final String TAG = "QosCallbackException"; + + // Types of exceptions supported // + /** {@hide} */ + public static final int EX_TYPE_FILTER_NONE = 0; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_NETWORK_RELEASED = 1; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4; + + /** + * Creates exception based off of a type and message. Not all types of exceptions accept a + * custom message. + * + * {@hide} + */ + @NonNull + static QosCallbackException createException(@ExceptionType final int type) { + switch (type) { + case EX_TYPE_FILTER_NETWORK_RELEASED: + return new QosCallbackException(new NetworkReleasedException()); + case EX_TYPE_FILTER_SOCKET_NOT_BOUND: + return new QosCallbackException(new SocketNotBoundException()); + case EX_TYPE_FILTER_NOT_SUPPORTED: + return new QosCallbackException(new UnsupportedOperationException( + "This device does not support the specified filter")); + case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED: + return new QosCallbackException( + new SocketLocalAddressChangedException()); + default: + Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'"); + return new QosCallbackException( + new RuntimeException("Unknown exception code: " + type)); + } + } + + /** + * @hide + */ + public QosCallbackException(@NonNull final String message) { + super(message); + } + + /** + * @hide + */ + public QosCallbackException(@NonNull final Throwable cause) { + super(cause); + } +} diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java new file mode 100644 index 0000000000..ab55002e02 --- /dev/null +++ b/framework/src/android/net/QosFilter.java @@ -0,0 +1,75 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import java.net.InetAddress; + +/** + * Provides the related filtering logic to the {@link NetworkAgent} to match {@link QosSession}s + * to their related {@link QosCallback}. + * + * Used by the {@link com.android.server.ConnectivityService} to validate a {@link QosCallback} + * is still able to receive a {@link QosSession}. + * + * @hide + */ +@SystemApi +public abstract class QosFilter { + + /** + * The constructor is kept hidden from outside this package to ensure that all derived types + * are known and properly handled when being passed to and from {@link NetworkAgent}. + * + * @hide + */ + QosFilter() { + } + + /** + * The network used with this filter. + * + * @return the registered {@link Network} + */ + @NonNull + public abstract Network getNetwork(); + + /** + * Validates that conditions have not changed such that no further {@link QosSession}s should + * be passed back to the {@link QosCallback} associated to this filter. + * + * @return the error code when present, otherwise the filter is valid + * + * @hide + */ + @QosCallbackException.ExceptionType + public abstract int validate(); + + /** + * Determines whether or not the parameters is a match for the filter. + * + * @param address the local address + * @param startPort the start of the port range + * @param endPort the end of the port range + * @return whether the parameters match the local address of the filter + */ + public abstract boolean matchesLocalAddress(@NonNull InetAddress address, + int startPort, int endPort); +} + diff --git a/framework/src/android/net/QosFilterParcelable.java b/framework/src/android/net/QosFilterParcelable.java new file mode 100644 index 0000000000..da3b2cf8ff --- /dev/null +++ b/framework/src/android/net/QosFilterParcelable.java @@ -0,0 +1,113 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.Objects; + +/** + * Aware of how to parcel different types of {@link QosFilter}s. Any new type of qos filter must + * have a specialized case written here. + *

    + * Specifically leveraged when transferring {@link QosFilter} from + * {@link com.android.server.ConnectivityService} to {@link NetworkAgent} when the filter is first + * registered. + *

    + * This is not meant to be used in other contexts. + * + * @hide + */ +public final class QosFilterParcelable implements Parcelable { + + private static final String LOG_TAG = QosFilterParcelable.class.getSimpleName(); + + // Indicates that the filter was not successfully written to the parcel. + private static final int NO_FILTER_PRESENT = 0; + + // The parcel is of type qos socket filter. + private static final int QOS_SOCKET_FILTER = 1; + + private final QosFilter mQosFilter; + + /** + * The underlying qos filter. + *

    + * Null only in the case parceling failed. + */ + @Nullable + public QosFilter getQosFilter() { + return mQosFilter; + } + + public QosFilterParcelable(@NonNull final QosFilter qosFilter) { + Objects.requireNonNull(qosFilter, "qosFilter must be non-null"); + + // NOTE: Normally a type check would belong here, but doing so breaks unit tests that rely + // on mocking qos filter. + mQosFilter = qosFilter; + } + + private QosFilterParcelable(final Parcel in) { + final int filterParcelType = in.readInt(); + + switch (filterParcelType) { + case QOS_SOCKET_FILTER: { + mQosFilter = new QosSocketFilter(QosSocketInfo.CREATOR.createFromParcel(in)); + break; + } + + case NO_FILTER_PRESENT: + default: { + mQosFilter = null; + } + } + } + + public static final Creator CREATOR = new Creator() { + @Override + public QosFilterParcelable createFromParcel(final Parcel in) { + return new QosFilterParcelable(in); + } + + @Override + public QosFilterParcelable[] newArray(final int size) { + return new QosFilterParcelable[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + if (mQosFilter instanceof QosSocketFilter) { + dest.writeInt(QOS_SOCKET_FILTER); + final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter; + qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0); + return; + } + dest.writeInt(NO_FILTER_PRESENT); + Log.e(LOG_TAG, "Parceling failed, unknown type of filter present: " + mQosFilter); + } +} diff --git a/framework/src/android/net/QosSession.java b/framework/src/android/net/QosSession.java new file mode 100644 index 0000000000..4f3bb77c58 --- /dev/null +++ b/framework/src/android/net/QosSession.java @@ -0,0 +1,136 @@ +/* + * 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 android.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Provides identifying information of a QoS session. Sent to an application through + * {@link QosCallback}. + * + * @hide + */ +@SystemApi +public final class QosSession implements Parcelable { + + /** + * The {@link QosSession} is a LTE EPS Session. + */ + public static final int TYPE_EPS_BEARER = 1; + + private final int mSessionId; + + private final int mSessionType; + + /** + * Gets the unique id of the session that is used to differentiate sessions across different + * types. + *

    + * Note: Different qos sessions can be provided by different actors. + * + * @return the unique id + */ + public long getUniqueId() { + return (long) mSessionType << 32 | mSessionId; + } + + /** + * Gets the session id that is unique within that type. + *

    + * Note: The session id is set by the actor providing the qos. It can be either manufactured by + * the actor, but also may have a particular meaning within that type. For example, using the + * bearer id as the session id for {@link android.telephony.data.EpsBearerQosSessionAttributes} + * is a straight forward way to keep the sessions unique from one another within that type. + * + * @return the id of the session + */ + public int getSessionId() { + return mSessionId; + } + + /** + * Gets the type of session. + */ + @QosSessionType + public int getSessionType() { + return mSessionType; + } + + /** + * Creates a {@link QosSession}. + * + * @param sessionId uniquely identifies the session across all sessions of the same type + * @param sessionType the type of session + */ + public QosSession(final int sessionId, @QosSessionType final int sessionType) { + //Ensures the session id is unique across types of sessions + mSessionId = sessionId; + mSessionType = sessionType; + } + + + @Override + public String toString() { + return "QosSession{" + + "mSessionId=" + mSessionId + + ", mSessionType=" + mSessionType + + '}'; + } + + /** + * Annotations for types of qos sessions. + */ + @IntDef(value = { + TYPE_EPS_BEARER, + }) + @interface QosSessionType {} + + private QosSession(final Parcel in) { + mSessionId = in.readInt(); + mSessionType = in.readInt(); + } + + @NonNull + public static final Creator CREATOR = new Creator() { + @NonNull + @Override + public QosSession createFromParcel(@NonNull final Parcel in) { + return new QosSession(in); + } + + @NonNull + @Override + public QosSession[] newArray(final int size) { + return new QosSession[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(mSessionId); + dest.writeInt(mSessionType); + } +} diff --git a/framework/src/android/net/QosSessionAttributes.java b/framework/src/android/net/QosSessionAttributes.java new file mode 100644 index 0000000000..7a885942d1 --- /dev/null +++ b/framework/src/android/net/QosSessionAttributes.java @@ -0,0 +1,30 @@ +/* + * 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 android.net; + +import android.annotation.SystemApi; + +/** + * Implemented by classes that encapsulate Qos related attributes that describe a Qos Session. + * + * Use the instanceof keyword to determine the underlying type. + * + * @hide + */ +@SystemApi +public interface QosSessionAttributes { +} diff --git a/framework/src/android/net/QosSocketFilter.java b/framework/src/android/net/QosSocketFilter.java new file mode 100644 index 0000000000..2080e68f5f --- /dev/null +++ b/framework/src/android/net/QosSocketFilter.java @@ -0,0 +1,166 @@ +/* + * 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 android.net; + +import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE; +import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.Objects; + +/** + * Filters a {@link QosSession} according to the binding on the provided {@link Socket}. + * + * @hide + */ +public class QosSocketFilter extends QosFilter { + + private static final String TAG = QosSocketFilter.class.getSimpleName(); + + @NonNull + private final QosSocketInfo mQosSocketInfo; + + /** + * Creates a {@link QosSocketFilter} based off of {@link QosSocketInfo}. + * + * @param qosSocketInfo the information required to filter and validate + */ + public QosSocketFilter(@NonNull final QosSocketInfo qosSocketInfo) { + Objects.requireNonNull(qosSocketInfo, "qosSocketInfo must be non-null"); + mQosSocketInfo = qosSocketInfo; + } + + /** + * Gets the parcelable qos socket info that was used to create the filter. + */ + @NonNull + public QosSocketInfo getQosSocketInfo() { + return mQosSocketInfo; + } + + /** + * Performs two validations: + * 1. If the socket is not bound, then return + * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND}. This is detected + * by checking the local address on the filter which becomes null when the socket is no + * longer bound. + * 2. In the scenario that the socket is now bound to a different local address, which can + * happen in the case of UDP, then + * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned. + * @return validation error code + */ + @Override + public int validate() { + final InetSocketAddress sa = getAddressFromFileDescriptor(); + if (sa == null) { + return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND; + } + + if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) { + return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED; + } + + return EX_TYPE_FILTER_NONE; + } + + /** + * The local address of the socket's binding. + * + * Note: If the socket is no longer bound, null is returned. + * + * @return the local address + */ + @Nullable + private InetSocketAddress getAddressFromFileDescriptor() { + final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor(); + if (parcelFileDescriptor == null) return null; + + final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor(); + if (fd == null) return null; + + final SocketAddress address; + try { + address = Os.getsockname(fd); + } catch (final ErrnoException e) { + Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e); + return null; + } + if (address instanceof InetSocketAddress) { + return (InetSocketAddress) address; + } + return null; + } + + /** + * The network used with this filter. + * + * @return the registered {@link Network} + */ + @NonNull + @Override + public Network getNetwork() { + return mQosSocketInfo.getNetwork(); + } + + /** + * @inheritDoc + */ + @Override + public boolean matchesLocalAddress(@NonNull final InetAddress address, final int startPort, + final int endPort) { + if (mQosSocketInfo.getLocalSocketAddress() == null) { + return false; + } + + return matchesLocalAddress(mQosSocketInfo.getLocalSocketAddress(), address, startPort, + endPort); + } + + /** + * Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)} with the + * filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}. + *

    + * This method exists for testing purposes since {@link QosSocketInfo} couldn't be mocked + * due to being final. + * + * @param filterSocketAddress the socket address of the filter + * @param address the address to compare the filterSocketAddressWith + * @param startPort the start of the port range to check + * @param endPort the end of the port range to check + */ + @VisibleForTesting + public static boolean matchesLocalAddress(@NonNull final InetSocketAddress filterSocketAddress, + @NonNull final InetAddress address, + final int startPort, final int endPort) { + return startPort <= filterSocketAddress.getPort() + && endPort >= filterSocketAddress.getPort() + && filterSocketAddress.getAddress().equals(address); + } +} diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java new file mode 100644 index 0000000000..d37c4691dd --- /dev/null +++ b/framework/src/android/net/QosSocketInfo.java @@ -0,0 +1,154 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.Objects; + +/** + * Used in conjunction with + * {@link ConnectivityManager#registerQosCallback} + * in order to receive Qos Sessions related to the local address and port of a bound {@link Socket}. + * + * @hide + */ +@SystemApi +public final class QosSocketInfo implements Parcelable { + + @NonNull + private final Network mNetwork; + + @NonNull + private final ParcelFileDescriptor mParcelFileDescriptor; + + @NonNull + private final InetSocketAddress mLocalSocketAddress; + + /** + * The {@link Network} the socket is on. + * + * @return the registered {@link Network} + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * The parcel file descriptor wrapped around the socket's file descriptor. + * + * @return the parcel file descriptor of the socket + */ + @NonNull + ParcelFileDescriptor getParcelFileDescriptor() { + return mParcelFileDescriptor; + } + + /** + * The local address of the socket passed into {@link QosSocketInfo(Network, Socket)}. + * The value does not reflect any changes that occur to the socket after it is first set + * in the constructor. + * + * @return the local address of the socket + */ + @NonNull + public InetSocketAddress getLocalSocketAddress() { + return mLocalSocketAddress; + } + + /** + * Creates a {@link QosSocketInfo} given a {@link Network} and bound {@link Socket}. The + * {@link Socket} must remain bound in order to receive {@link QosSession}s. + * + * @param network the network + * @param socket the bound {@link Socket} + */ + public QosSocketInfo(@NonNull final Network network, @NonNull final Socket socket) + throws IOException { + Objects.requireNonNull(socket, "socket cannot be null"); + + mNetwork = Objects.requireNonNull(network, "network cannot be null"); + mParcelFileDescriptor = ParcelFileDescriptor.dup(socket.getFileDescriptor$()); + mLocalSocketAddress = + new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort()); + } + + /* Parcelable methods */ + private QosSocketInfo(final Parcel in) { + mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in)); + mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in); + + final int addressLength = in.readInt(); + mLocalSocketAddress = readSocketAddress(in, addressLength); + } + + private InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) { + final byte[] address = new byte[addressLength]; + in.readByteArray(address); + final int port = in.readInt(); + + try { + return new InetSocketAddress(InetAddress.getByAddress(address), port); + } catch (final UnknownHostException e) { + /* The catch block was purposely left empty. UnknownHostException will never be thrown + since the address provided is numeric and non-null. */ + } + return new InetSocketAddress(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + mNetwork.writeToParcel(dest, 0); + mParcelFileDescriptor.writeToParcel(dest, 0); + + final byte[] address = mLocalSocketAddress.getAddress().getAddress(); + dest.writeInt(address.length); + dest.writeByteArray(address); + dest.writeInt(mLocalSocketAddress.getPort()); + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @NonNull + @Override + public QosSocketInfo createFromParcel(final Parcel in) { + return new QosSocketInfo(in); + } + + @NonNull + @Override + public QosSocketInfo[] newArray(final int size) { + return new QosSocketInfo[size]; + } + }; +} diff --git a/framework/src/android/net/SocketLocalAddressChangedException.java b/framework/src/android/net/SocketLocalAddressChangedException.java new file mode 100644 index 0000000000..9daad83fd1 --- /dev/null +++ b/framework/src/android/net/SocketLocalAddressChangedException.java @@ -0,0 +1,32 @@ +/* + * 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 android.net; + +import android.annotation.SystemApi; + +/** + * Thrown when the local address of the socket has changed. + * + * @hide + */ +@SystemApi +public class SocketLocalAddressChangedException extends Exception { + /** @hide */ + public SocketLocalAddressChangedException() { + super("The local address of the socket changed"); + } +} diff --git a/framework/src/android/net/SocketNotBoundException.java b/framework/src/android/net/SocketNotBoundException.java new file mode 100644 index 0000000000..b1d7026ac9 --- /dev/null +++ b/framework/src/android/net/SocketNotBoundException.java @@ -0,0 +1,32 @@ +/* + * 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 android.net; + +import android.annotation.SystemApi; + +/** + * Thrown when a previously bound socket becomes unbound. + * + * @hide + */ +@SystemApi +public class SocketNotBoundException extends Exception { + /** @hide */ + public SocketNotBoundException() { + super("The socket is unbound"); + } +} From a1433566fec43bce09844aed6f9a36d4028a5e71 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 22 Feb 2021 07:28:12 +0000 Subject: [PATCH 056/232] Move Proxy, PacProxySelector out of Connectivity PacProxySelector is tied to IProxyService, which does not have a formal API. Proxy is the interface with ConnectivityService, and all its methods are public or module API. Bug: 171540887 Test: m Change-Id: I8ceba961a81661c3e11d8179955b594d3cab6ff7 --- framework/api/current.txt | 10 - framework/api/module-lib-current.txt | 4 - .../src/android/net/PacProxySelector.java | 138 ---------- framework/src/android/net/Proxy.java | 248 ------------------ 4 files changed, 400 deletions(-) delete mode 100644 framework/src/android/net/PacProxySelector.java delete mode 100644 framework/src/android/net/Proxy.java diff --git a/framework/api/current.txt b/framework/api/current.txt index d4262a807a..4bfcda3826 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -400,16 +400,6 @@ package android.net { method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); } - public final class Proxy { - ctor public Proxy(); - method @Deprecated public static String getDefaultHost(); - method @Deprecated public static int getDefaultPort(); - method @Deprecated public static String getHost(android.content.Context); - method @Deprecated public static int getPort(android.content.Context); - field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; - field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; - } - public class ProxyInfo implements android.os.Parcelable { ctor public ProxyInfo(@Nullable android.net.ProxyInfo); method public static android.net.ProxyInfo buildDirectProxy(String, int); diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 3af855ec1e..a9fd6f2485 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -23,10 +23,6 @@ package android.net { field public static final int TRANSPORT_TEST = 7; // 0x7 } - public final class Proxy { - method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); - } - public final class TcpRepairWindow { ctor public TcpRepairWindow(int, int, int, int, int, int); field public final int maxWindow; diff --git a/framework/src/android/net/PacProxySelector.java b/framework/src/android/net/PacProxySelector.java deleted file mode 100644 index 326943a27d..0000000000 --- a/framework/src/android/net/PacProxySelector.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2013 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; - -import android.os.ServiceManager; -import android.util.Log; - -import com.android.net.IProxyService; - -import com.google.android.collect.Lists; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.Proxy.Type; -import java.net.ProxySelector; -import java.net.SocketAddress; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.List; - -/** - * @hide - */ -public class PacProxySelector extends ProxySelector { - private static final String TAG = "PacProxySelector"; - public static final String PROXY_SERVICE = "com.android.net.IProxyService"; - private static final String SOCKS = "SOCKS "; - private static final String PROXY = "PROXY "; - - private IProxyService mProxyService; - private final List mDefaultList; - - public PacProxySelector() { - mProxyService = IProxyService.Stub.asInterface( - ServiceManager.getService(PROXY_SERVICE)); - if (mProxyService == null) { - // Added because of b10267814 where mako is restarting. - Log.e(TAG, "PacProxyInstaller: no proxy service"); - } - mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY); - } - - @Override - public List select(URI uri) { - if (mProxyService == null) { - mProxyService = IProxyService.Stub.asInterface( - ServiceManager.getService(PROXY_SERVICE)); - } - if (mProxyService == null) { - Log.e(TAG, "select: no proxy service return NO_PROXY"); - return Lists.newArrayList(java.net.Proxy.NO_PROXY); - } - String response = null; - String urlString; - try { - // Strip path and username/password from URI so it's not visible to PAC script. The - // path often contains credentials the app does not want exposed to a potentially - // malicious PAC script. - if (!"http".equalsIgnoreCase(uri.getScheme())) { - uri = new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), "/", null, null); - } - urlString = uri.toURL().toString(); - } catch (URISyntaxException e) { - urlString = uri.getHost(); - } catch (MalformedURLException e) { - urlString = uri.getHost(); - } - try { - response = mProxyService.resolvePacFile(uri.getHost(), urlString); - } catch (Exception e) { - Log.e(TAG, "Error resolving PAC File", e); - } - if (response == null) { - return mDefaultList; - } - - return parseResponse(response); - } - - private static List parseResponse(String response) { - String[] split = response.split(";"); - List ret = Lists.newArrayList(); - for (String s : split) { - String trimmed = s.trim(); - if (trimmed.equals("DIRECT")) { - ret.add(java.net.Proxy.NO_PROXY); - } else if (trimmed.startsWith(PROXY)) { - Proxy proxy = proxyFromHostPort(Type.HTTP, trimmed.substring(PROXY.length())); - if (proxy != null) { - ret.add(proxy); - } - } else if (trimmed.startsWith(SOCKS)) { - Proxy proxy = proxyFromHostPort(Type.SOCKS, trimmed.substring(SOCKS.length())); - if (proxy != null) { - ret.add(proxy); - } - } - } - if (ret.size() == 0) { - ret.add(java.net.Proxy.NO_PROXY); - } - return ret; - } - - private static Proxy proxyFromHostPort(Proxy.Type type, String hostPortString) { - try { - String[] hostPort = hostPortString.split(":"); - String host = hostPort[0]; - int port = Integer.parseInt(hostPort[1]); - return new Proxy(type, InetSocketAddress.createUnresolved(host, port)); - } catch (NumberFormatException|ArrayIndexOutOfBoundsException e) { - Log.d(TAG, "Unable to parse proxy " + hostPortString + " " + e); - return null; - } - } - - @Override - public void connectFailed(URI uri, SocketAddress address, IOException failure) { - - } - -} diff --git a/framework/src/android/net/Proxy.java b/framework/src/android/net/Proxy.java deleted file mode 100644 index 77c8a4f457..0000000000 --- a/framework/src/android/net/Proxy.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2007 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; - -import android.annotation.Nullable; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.Context; -import android.os.Build; -import android.text.TextUtils; -import android.util.Log; - -import com.android.net.module.util.ProxyUtils; - -import java.net.InetSocketAddress; -import java.net.ProxySelector; -import java.net.URI; -import java.util.List; - -/** - * A convenience class for accessing the user and default proxy - * settings. - */ -public final class Proxy { - - private static final String TAG = "Proxy"; - - private static final ProxySelector sDefaultProxySelector; - - /** - * Used to notify an app that's caching the proxy that either the default - * connection has changed or any connection's proxy has changed. The new - * proxy should be queried using {@link ConnectivityManager#getDefaultProxy()}. - * - *

    This is a protected intent that can only be sent by the system - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; - /** - * Intent extra included with {@link #PROXY_CHANGE_ACTION} intents. - * It describes the new proxy being used (as a {@link ProxyInfo} object). - * @deprecated Because {@code PROXY_CHANGE_ACTION} is sent whenever the proxy - * for any network on the system changes, applications should always use - * {@link ConnectivityManager#getDefaultProxy()} or - * {@link ConnectivityManager#getLinkProperties(Network)}.{@link LinkProperties#getHttpProxy()} - * to get the proxy for the Network(s) they are using. - */ - @Deprecated - public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; - - private static ConnectivityManager sConnectivityManager = null; - - static { - sDefaultProxySelector = ProxySelector.getDefault(); - } - - /** - * Return the proxy object to be used for the URL given as parameter. - * @param ctx A Context used to get the settings for the proxy host. - * @param url A URL to be accessed. Used to evaluate exclusion list. - * @return Proxy (java.net) object containing the host name. If the - * user did not set a hostname it returns the default host. - * A null value means that no host is to be used. - * {@hide} - */ - @UnsupportedAppUsage - public static final java.net.Proxy getProxy(Context ctx, String url) { - String host = ""; - if ((url != null) && !isLocalHost(host)) { - URI uri = URI.create(url); - ProxySelector proxySelector = ProxySelector.getDefault(); - - List proxyList = proxySelector.select(uri); - - if (proxyList.size() > 0) { - return proxyList.get(0); - } - } - return java.net.Proxy.NO_PROXY; - } - - - /** - * Return the proxy host set by the user. - * @param ctx A Context used to get the settings for the proxy host. - * @return String containing the host name. If the user did not set a host - * name it returns the default host. A null value means that no - * host is to be used. - * @deprecated Use standard java vm proxy values to find the host, port - * and exclusion list. This call ignores the exclusion list. - */ - @Deprecated - public static final String getHost(Context ctx) { - java.net.Proxy proxy = getProxy(ctx, null); - if (proxy == java.net.Proxy.NO_PROXY) return null; - try { - return ((InetSocketAddress)(proxy.address())).getHostName(); - } catch (Exception e) { - return null; - } - } - - /** - * Return the proxy port set by the user. - * @param ctx A Context used to get the settings for the proxy port. - * @return The port number to use or -1 if no proxy is to be used. - * @deprecated Use standard java vm proxy values to find the host, port - * and exclusion list. This call ignores the exclusion list. - */ - @Deprecated - public static final int getPort(Context ctx) { - java.net.Proxy proxy = getProxy(ctx, null); - if (proxy == java.net.Proxy.NO_PROXY) return -1; - try { - return ((InetSocketAddress)(proxy.address())).getPort(); - } catch (Exception e) { - return -1; - } - } - - /** - * Return the default proxy host specified by the carrier. - * @return String containing the host name or null if there is no proxy for - * this carrier. - * @deprecated Use standard java vm proxy values to find the host, port and - * exclusion list. This call ignores the exclusion list and no - * longer reports only mobile-data apn-based proxy values. - */ - @Deprecated - public static final String getDefaultHost() { - String host = System.getProperty("http.proxyHost"); - if (TextUtils.isEmpty(host)) return null; - return host; - } - - /** - * Return the default proxy port specified by the carrier. - * @return The port number to be used with the proxy host or -1 if there is - * no proxy for this carrier. - * @deprecated Use standard java vm proxy values to find the host, port and - * exclusion list. This call ignores the exclusion list and no - * longer reports only mobile-data apn-based proxy values. - */ - @Deprecated - public static final int getDefaultPort() { - if (getDefaultHost() == null) return -1; - try { - return Integer.parseInt(System.getProperty("http.proxyPort")); - } catch (NumberFormatException e) { - return -1; - } - } - - private static final boolean isLocalHost(String host) { - if (host == null) { - return false; - } - try { - if (host != null) { - if (host.equalsIgnoreCase("localhost")) { - return true; - } - if (InetAddresses.parseNumericAddress(host).isLoopbackAddress()) { - return true; - } - } - } catch (IllegalArgumentException iex) { - } - return false; - } - - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @Deprecated - public static void setHttpProxySystemProperty(ProxyInfo p) { - setHttpProxyConfiguration(p); - } - - /** - * Set HTTP proxy configuration for the process to match the provided ProxyInfo. - * - * If the provided ProxyInfo is null, the proxy configuration will be cleared. - * @hide - */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static void setHttpProxyConfiguration(@Nullable ProxyInfo p) { - String host = null; - String port = null; - String exclList = null; - Uri pacFileUrl = Uri.EMPTY; - if (p != null) { - host = p.getHost(); - port = Integer.toString(p.getPort()); - exclList = ProxyUtils.exclusionListAsString(p.getExclusionList()); - pacFileUrl = p.getPacFileUrl(); - } - setHttpProxyConfiguration(host, port, exclList, pacFileUrl); - } - - /** @hide */ - public static void setHttpProxyConfiguration(String host, String port, String exclList, - Uri pacFileUrl) { - if (exclList != null) exclList = exclList.replace(",", "|"); - if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList); - if (host != null) { - System.setProperty("http.proxyHost", host); - System.setProperty("https.proxyHost", host); - } else { - System.clearProperty("http.proxyHost"); - System.clearProperty("https.proxyHost"); - } - if (port != null) { - System.setProperty("http.proxyPort", port); - System.setProperty("https.proxyPort", port); - } else { - System.clearProperty("http.proxyPort"); - System.clearProperty("https.proxyPort"); - } - if (exclList != null) { - System.setProperty("http.nonProxyHosts", exclList); - System.setProperty("https.nonProxyHosts", exclList); - } else { - System.clearProperty("http.nonProxyHosts"); - System.clearProperty("https.nonProxyHosts"); - } - if (!Uri.EMPTY.equals(pacFileUrl)) { - ProxySelector.setDefault(new PacProxySelector()); - } else { - ProxySelector.setDefault(sDefaultProxySelector); - } - } -} From 96a931b5b25874aa040a66599034dafe88233195 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 2 Mar 2021 03:28:50 +0000 Subject: [PATCH 057/232] Add modules-utils-os to service-connectivity service-connectivity needs the library so that module utilities are statically linked, not picked up from the framework jar as hidden symbols. Bug: 170598012 Change-Id: I1045b3784a4bdb902f44d848ccddb304986631c3 Test: m --- service/Android.bp | 1 + service/jarjar-rules.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/service/Android.bp b/service/Android.bp index f20b89fb84..e65b7b423b 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -63,6 +63,7 @@ java_library { "unsupportedappusage", ], static_libs: [ + "modules-utils-os", "net-utils-device-common", "net-utils-framework-common", "netd-client", diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt index ef53ebb43c..d8205bf780 100644 --- a/service/jarjar-rules.txt +++ b/service/jarjar-rules.txt @@ -1 +1,2 @@ -rule com.android.net.module.util.** com.android.connectivity.util.@1 \ No newline at end of file +rule com.android.net.module.util.** com.android.connectivity.net-utils.@1 +rule com.android.modules.utils.** com.android.connectivity.modules-utils.@1 \ No newline at end of file From 24d950ce5a7d8c4018556583fa23c5b7f02d13fb Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 2 Mar 2021 13:56:38 +0900 Subject: [PATCH 058/232] Move OemNetworkPreferences aidl to connectivity Move the IOnSetOemNetworkPreferenceListener aidl definition to framework-connectivity. The interface is an internal implementation detail of framework-connectivity, so it should be built inside the jar. Bug: 181512874 Test: m Change-Id: I898049b50fc620ee629587a9303f058e0a6d0272 --- .../IOnSetOemNetworkPreferenceListener.aidl | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl diff --git a/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl new file mode 100644 index 0000000000..7979afc54f --- /dev/null +++ b/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl @@ -0,0 +1,23 @@ +/** + * + * 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 android.net; + +/** @hide */ +oneway interface IOnSetOemNetworkPreferenceListener { + void onComplete(); +} From 5115480e8f5d0bdf4d82d0809cdc5ccf0f1f78e7 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 2 Mar 2021 12:12:49 +0900 Subject: [PATCH 059/232] Move UidRange to connectivity UidRange is a data class that is an implementation detail of Connectivity. Move it to the connectivity boundary. Remaining usages of UidRange outside of Connectivity (in VPN) should be migrated to other classes, like Range or UidRangeParcel. Bug: 181512874 Test: m Change-Id: I6f2e3685ad1c07171dd90480d1e546329de8732d --- framework/src/android/net/UidRange.aidl | 24 ++++ framework/src/android/net/UidRange.java | 152 ++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 framework/src/android/net/UidRange.aidl create mode 100644 framework/src/android/net/UidRange.java diff --git a/framework/src/android/net/UidRange.aidl b/framework/src/android/net/UidRange.aidl new file mode 100644 index 0000000000..f70fc8e2fe --- /dev/null +++ b/framework/src/android/net/UidRange.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2018 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; + +/** + * An inclusive range of UIDs. + * + * {@hide} + */ +parcelable UidRange; \ No newline at end of file diff --git a/framework/src/android/net/UidRange.java b/framework/src/android/net/UidRange.java new file mode 100644 index 0000000000..26518d32ed --- /dev/null +++ b/framework/src/android/net/UidRange.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2014 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; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; + +import java.util.Collection; + +/** + * An inclusive range of UIDs. + * + * @hide + */ +public final class UidRange implements Parcelable { + public final int start; + public final int stop; + + public UidRange(int startUid, int stopUid) { + if (startUid < 0) throw new IllegalArgumentException("Invalid start UID."); + if (stopUid < 0) throw new IllegalArgumentException("Invalid stop UID."); + if (startUid > stopUid) throw new IllegalArgumentException("Invalid UID range."); + start = startUid; + stop = stopUid; + } + + /** Creates a UidRange for the specified user. */ + public static UidRange createForUser(UserHandle user) { + final UserHandle nextUser = UserHandle.of(user.getIdentifier() + 1); + final int start = UserHandle.getUid(user, 0 /* appId */); + final int end = UserHandle.getUid(nextUser, 0) - 1; + return new UidRange(start, end); + } + + /** Returns the smallest user Id which is contained in this UidRange */ + public int getStartUser() { + return UserHandle.getUserHandleForUid(start).getIdentifier(); + } + + /** Returns the largest user Id which is contained in this UidRange */ + public int getEndUser() { + return UserHandle.getUserHandleForUid(stop).getIdentifier(); + } + + /** Returns whether the UidRange contains the specified UID. */ + public boolean contains(int uid) { + return start <= uid && uid <= stop; + } + + /** + * Returns the count of UIDs in this range. + */ + public int count() { + return 1 + stop - start; + } + + /** + * @return {@code true} if this range contains every UID contained by the {@code other} range. + */ + public boolean containsRange(UidRange other) { + return start <= other.start && other.stop <= stop; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + start; + result = 31 * result + stop; + return result; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o instanceof UidRange) { + UidRange other = (UidRange) o; + return start == other.start && stop == other.stop; + } + return false; + } + + @Override + public String toString() { + return start + "-" + stop; + } + + // Implement the Parcelable interface + // TODO: Consider making this class no longer parcelable, since all users are likely in the + // system server. + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(start); + dest.writeInt(stop); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + @Override + public UidRange createFromParcel(Parcel in) { + int start = in.readInt(); + int stop = in.readInt(); + + return new UidRange(start, stop); + } + @Override + public UidRange[] newArray(int size) { + return new UidRange[size]; + } + }; + + /** + * Returns whether any of the UidRange in the collection contains the specified uid + * + * @param ranges The collection of UidRange to check + * @param uid the uid in question + * @return {@code true} if the uid is contained within the ranges, {@code false} otherwise + * + * @see UidRange#contains(int) + */ + public static boolean containsUid(Collection ranges, int uid) { + if (ranges == null) return false; + for (UidRange range : ranges) { + if (range.contains(uid)) { + return true; + } + } + return false; + } +} From ae9a64d7daf28cbeb871c669a979bc142d24bd46 Mon Sep 17 00:00:00 2001 From: Sarah Chin Date: Wed, 3 Feb 2021 12:00:20 -0800 Subject: [PATCH 060/232] APIs for 5G slicing Create TrafficDescriptor class Create new APN ENTERPRISE Update setupDataCall and DataCallResponse to take TrafficDescriptor and matchAllRuleAllowed Move ApnTypes from Annotation to ApnSetting Bug: 179312227 Test: atest FrameworksTelephonyTests Change-Id: I7433976bfe25bcb2af85ffb9338959cbcc9f42f3 Merged-In: I7433976bfe25bcb2af85ffb9338959cbcc9f42f3 --- framework/api/current.txt | 1 + .../src/android/net/NetworkCapabilities.java | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index 4bfcda3826..a8f1a4d2a7 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -302,6 +302,7 @@ package android.net { field public static final int NET_CAPABILITY_CBS = 5; // 0x5 field public static final int NET_CAPABILITY_DUN = 2; // 0x2 field public static final int NET_CAPABILITY_EIMS = 10; // 0xa + field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13 field public static final int NET_CAPABILITY_FOTA = 3; // 0x3 field public static final int NET_CAPABILITY_IA = 7; // 0x7 diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 26d14cbfaa..cd76f409b0 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -205,6 +205,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_OEM_PRIVATE, NET_CAPABILITY_VEHICLE_INTERNAL, NET_CAPABILITY_NOT_VCN_MANAGED, + NET_CAPABILITY_ENTERPRISE, }) public @interface NetCapability { } @@ -415,8 +416,17 @@ public final class NetworkCapabilities implements Parcelable { @SystemApi public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; + /** + * Indicates that this network is intended for enterprise use. + *

    + * 5G URSP rules may indicate that all data should use a connection dedicated for enterprise + * use. If the enterprise capability is requested, all enterprise traffic will be routed over + * the connection with this capability. + */ + public static final int NET_CAPABILITY_ENTERPRISE = 29; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VCN_MANAGED; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_ENTERPRISE; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -474,7 +484,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_MCX) | (1 << NET_CAPABILITY_RCS) | (1 << NET_CAPABILITY_VEHICLE_INTERNAL) - | (1 << NET_CAPABILITY_XCAP); + | (1 << NET_CAPABILITY_XCAP) + | (1 << NET_CAPABILITY_ENTERPRISE); /** * Capabilities that force network to be restricted. @@ -2028,8 +2039,9 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY"; case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE"; - case NET_CAPABILITY_VEHICLE_INTERNAL: return "NET_CAPABILITY_VEHICLE_INTERNAL"; + case NET_CAPABILITY_VEHICLE_INTERNAL: return "VEHICLE_INTERNAL"; case NET_CAPABILITY_NOT_VCN_MANAGED: return "NOT_VCN_MANAGED"; + case NET_CAPABILITY_ENTERPRISE: return "ENTERPRISE"; default: return Integer.toString(capability); } } From 1c7789742b5cf0a9d56e27b95bd0c49bb3fbd590 Mon Sep 17 00:00:00 2001 From: lifr Date: Tue, 2 Mar 2021 17:12:27 +0800 Subject: [PATCH 061/232] [CS05]Remove the hidden API usage of MetricsLogger Legacy metrics are unused and deprecated, so they are being removed. Therefore, delete the usage of the hidden MetricsLogger API. Bug: 157966864 Test: atest CtsNetTestCases atest CaptivePortalTest atest ConnectivityServiceTest Change-Id: I51241f5d50ec580015882c84dd917b015c700c7c --- framework/api/system-current.txt | 2 +- framework/src/android/net/CaptivePortal.java | 7 +++---- framework/src/android/net/ICaptivePortal.aidl | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 373fa3c240..f5972fa340 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -2,7 +2,7 @@ package android.net { public class CaptivePortal implements android.os.Parcelable { - method public void logEvent(int, @NonNull String); + method @Deprecated public void logEvent(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); method public void useNetwork(); field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 diff --git a/framework/src/android/net/CaptivePortal.java b/framework/src/android/net/CaptivePortal.java index 269bbf20c8..4a7b601642 100644 --- a/framework/src/android/net/CaptivePortal.java +++ b/framework/src/android/net/CaptivePortal.java @@ -160,12 +160,11 @@ public class CaptivePortal implements Parcelable { * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto. * @param packageName captive portal application package name. * @hide + * @deprecated The event will not be logged in Android S and above. The + * caller is migrating to statsd. */ + @Deprecated @SystemApi public void logEvent(int eventId, @NonNull String packageName) { - try { - ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName); - } catch (RemoteException e) { - } } } diff --git a/framework/src/android/net/ICaptivePortal.aidl b/framework/src/android/net/ICaptivePortal.aidl index fe21905c70..e35f8d46af 100644 --- a/framework/src/android/net/ICaptivePortal.aidl +++ b/framework/src/android/net/ICaptivePortal.aidl @@ -23,5 +23,4 @@ package android.net; oneway interface ICaptivePortal { void appRequest(int request); void appResponse(int response); - void logEvent(int eventId, String packageName); } From d9264b1fa3b64b719ef1245983b593e7bbdaf46a Mon Sep 17 00:00:00 2001 From: lucaslin Date: Thu, 4 Mar 2021 09:29:30 +0800 Subject: [PATCH 062/232] Use ArraySet#add() instead of ArraySet#append() ArraySet#append() is a hidden API which is not accessible for mainline module, use public one - ArraySet#add() instead. Bug: 170598012 Test: atest FrameworksNetTests Change-Id: I0742e2ec7aff008141b1de6d10eeca2910df71b1 --- framework/src/android/net/NetworkCapabilities.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index cd76f409b0..fa0e914d2c 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -1864,7 +1864,7 @@ public final class NetworkCapabilities implements Parcelable { final ArraySet result = new ArraySet<>(size); for (int i = 0; i < size; i++) { final T value = in.readParcelable(loader); - result.append(value); + result.add(value); } return result; } From eaff72d5449671fdf5a89f8b5007e78eb91eacec Mon Sep 17 00:00:00 2001 From: lucaslin Date: Thu, 4 Mar 2021 09:38:21 +0800 Subject: [PATCH 063/232] Replace withCleanCallingIdentity with [clear|restore]CallingIdentity To prevent using @hide method - withCleanCallingIdentity() from mainline module, use clearCallingIdentity() & restoreCallingIdentity() instead. Bug: 172183305 Test: FrameworksNetTests, CtsNetTestCasesLatestSdk Change-Id: I8221bb8717ba6809c5087ea2808cd4ccef948cfd --- framework/src/android/net/ConnectivityManager.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 66e7da43cb..1f2975c8a3 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -2886,10 +2886,14 @@ public class ConnectivityManager { ResultReceiver wrappedListener = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { - Binder.withCleanCallingIdentity(() -> - executor.execute(() -> { - listener.onTetheringEntitlementResult(resultCode); - })); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + listener.onTetheringEntitlementResult(resultCode); + }); + } finally { + Binder.restoreCallingIdentity(token); + } } }; From b12113700c3ed569821557ef657c40244c1fb803 Mon Sep 17 00:00:00 2001 From: junyulai Date: Wed, 3 Mar 2021 12:09:05 +0800 Subject: [PATCH 064/232] [FUI22] Support getAllNetworkStateSnapshot Currently, ConnectivityService has getAllNetworkState but it is not ideal to expose as system API since the plan is to get rid of NetworkState. Thus, create a new one that returns NetworkStateSnapshot to fulfill the needs. Note the original getAllNetworkState cannot be deleted now since it has @UnsupportedAppUsage annotation. Test: atest FrameworksNetTests Bug: 174123988 Change-Id: Icddd434552b0e9ecbc8299e7242ec88cf3145aca --- framework/api/module-lib-current.txt | 1 + .../src/android/net/ConnectivityManager.java | 19 +++++++++++++++++++ .../src/android/net/IConnectivityManager.aidl | 3 +++ 3 files changed, 23 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index a9fd6f2485..d2ed73ef82 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -6,6 +6,7 @@ package android.net { } public class ConnectivityManager { + method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshot(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 66e7da43cb..a6dc9ce051 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1258,6 +1258,25 @@ public class ConnectivityManager { } } + /** + * Return a list of {@link NetworkStateSnapshot}s, one for each network that is currently + * connected. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @NonNull + public List getAllNetworkStateSnapshot() { + try { + return mService.getAllNetworkStateSnapshot(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Returns the {@link Network} object currently serving a given type, or * null if the given type is not connected. diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 160338d396..cd49258d1c 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -31,6 +31,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.NetworkState; +import android.net.NetworkStateSnapshot; import android.net.OemNetworkPreferences; import android.net.ProxyInfo; import android.net.UidRange; @@ -79,6 +80,8 @@ interface IConnectivityManager @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) NetworkState[] getAllNetworkState(); + List getAllNetworkStateSnapshot(); + boolean isActiveNetworkMetered(); boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, From 919a491443ff4ef41e851c048656af17abc467da Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 5 Mar 2021 08:53:36 +0900 Subject: [PATCH 065/232] Move NetworkState to Connectivity NetworkState is becoming an internal implementation class, with NetworkStateSnapshot replacing it as a proper API. Considering this it belongs inside Connectivity. Bug: 174123988 Test: m Change-Id: I201f1a07c50d9da31e33f5c207875da8863ef57c --- framework/src/android/net/NetworkState.java | 130 ++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 framework/src/android/net/NetworkState.java diff --git a/framework/src/android/net/NetworkState.java b/framework/src/android/net/NetworkState.java new file mode 100644 index 0000000000..d01026566c --- /dev/null +++ b/framework/src/android/net/NetworkState.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2011 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; + +/** + * Snapshot of network state. + * + * @hide + */ +public class NetworkState implements Parcelable { + private static final boolean VALIDATE_ROAMING_STATE = false; + + // TODO: remove and make members @NonNull. + public static final NetworkState EMPTY = new NetworkState(); + + public final NetworkInfo networkInfo; + public final LinkProperties linkProperties; + public final NetworkCapabilities networkCapabilities; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public final Network network; + public final String subscriberId; + public final int legacyNetworkType; + + private NetworkState() { + networkInfo = null; + linkProperties = null; + networkCapabilities = null; + network = null; + subscriberId = null; + legacyNetworkType = 0; + } + + public NetworkState(int legacyNetworkType, @NonNull LinkProperties linkProperties, + @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network, + @Nullable String subscriberId) { + this(legacyNetworkType, new NetworkInfo(legacyNetworkType, 0, null, null), linkProperties, + networkCapabilities, network, subscriberId); + } + + // Constructor that used internally in ConnectivityService mainline module. + public NetworkState(@NonNull NetworkInfo networkInfo, @NonNull LinkProperties linkProperties, + @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network, + @Nullable String subscriberId) { + this(networkInfo.getType(), networkInfo, linkProperties, + networkCapabilities, network, subscriberId); + } + + public NetworkState(int legacyNetworkType, @NonNull NetworkInfo networkInfo, + @NonNull LinkProperties linkProperties, + @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network, + @Nullable String subscriberId) { + this.networkInfo = networkInfo; + this.linkProperties = linkProperties; + this.networkCapabilities = networkCapabilities; + this.network = network; + this.subscriberId = subscriberId; + this.legacyNetworkType = legacyNetworkType; + + // This object is an atomic view of a network, so the various components + // should always agree on roaming state. + if (VALIDATE_ROAMING_STATE && networkInfo != null && networkCapabilities != null) { + if (networkInfo.isRoaming() == networkCapabilities + .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) { + Slog.wtf("NetworkState", "Roaming state disagreement between " + networkInfo + + " and " + networkCapabilities); + } + } + } + + @UnsupportedAppUsage + public NetworkState(Parcel in) { + networkInfo = in.readParcelable(null); + linkProperties = in.readParcelable(null); + networkCapabilities = in.readParcelable(null); + network = in.readParcelable(null); + subscriberId = in.readString(); + legacyNetworkType = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(networkInfo, flags); + out.writeParcelable(linkProperties, flags); + out.writeParcelable(networkCapabilities, flags); + out.writeParcelable(network, flags); + out.writeString(subscriberId); + out.writeInt(legacyNetworkType); + } + + @UnsupportedAppUsage + @NonNull + public static final Creator CREATOR = new Creator() { + @Override + public NetworkState createFromParcel(Parcel in) { + return new NetworkState(in); + } + + @Override + public NetworkState[] newArray(int size) { + return new NetworkState[size]; + } + }; +} From 43042a95cd0acea9f1c722a14359d836539540aa Mon Sep 17 00:00:00 2001 From: David Su Date: Fri, 5 Mar 2021 01:21:21 +0000 Subject: [PATCH 066/232] Emphasize possibility of multiple networks with same transport Bug: 180125982 Test: compiles Change-Id: I70d312c29b7d7b773ebb8d70043f239a6c1aae15 --- framework/src/android/net/NetworkCapabilities.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index cd76f409b0..0b87ba197b 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -2291,7 +2291,8 @@ public final class NetworkCapabilities implements Parcelable { * For example {@code TRANSPORT_WIFI} and {@code TRANSPORT_ETHERNET} added to a * {@code NetworkCapabilities} would cause either a Wi-Fi network or an Ethernet network * to be selected. This is logically different than - * {@code NetworkCapabilities.NET_CAPABILITY_*}. + * {@code NetworkCapabilities.NET_CAPABILITY_*}. Also note that multiple networks with the + * same transport type may be active concurrently. * * @param transportType the transport type to be added or removed. * @return this builder From 991f65f1644b158ff6b113fb5a59a2e4b70042ce Mon Sep 17 00:00:00 2001 From: lifr Date: Thu, 4 Mar 2021 14:08:08 +0800 Subject: [PATCH 067/232] [CS10]Remove the hidden API usage of BitUtils The connection service will become the main line module. It is difficult to include BitUtils in the module. and so Move the hidden API needed in BitUtils to NetworkCapabilitiesUtils. Bug: 170598012 Test: atest ConnectivityServiceTest atest NetworkCapabilitiesTest atest DnsUtilsTest Change-Id: Ibc81827e25a54fc3ff94f78d810fe4f5073e3a98 --- .../src/android/net/NetworkCapabilities.java | 22 ++++++++++--------- framework/src/android/net/util/DnsUtils.java | 4 +--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index cd76f409b0..ab58f1b41a 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -34,9 +34,9 @@ import android.util.ArraySet; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.BitUtils; import com.android.internal.util.Preconditions; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.NetworkCapabilitiesUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -610,7 +610,7 @@ public final class NetworkCapabilities implements Parcelable { */ @UnsupportedAppUsage public @NetCapability int[] getCapabilities() { - return BitUtils.unpackBits(mNetworkCapabilities); + return NetworkCapabilitiesUtils.unpackBits(mNetworkCapabilities); } /** @@ -620,7 +620,7 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public @NetCapability int[] getUnwantedCapabilities() { - return BitUtils.unpackBits(mUnwantedNetworkCapabilities); + return NetworkCapabilitiesUtils.unpackBits(mUnwantedNetworkCapabilities); } @@ -632,8 +632,8 @@ public final class NetworkCapabilities implements Parcelable { */ public void setCapabilities(@NetCapability int[] capabilities, @NetCapability int[] unwantedCapabilities) { - mNetworkCapabilities = BitUtils.packBits(capabilities); - mUnwantedNetworkCapabilities = BitUtils.packBits(unwantedCapabilities); + mNetworkCapabilities = NetworkCapabilitiesUtils.packBits(capabilities); + mUnwantedNetworkCapabilities = NetworkCapabilitiesUtils.packBits(unwantedCapabilities); } /** @@ -688,7 +688,7 @@ public final class NetworkCapabilities implements Parcelable { & NON_REQUESTABLE_CAPABILITIES; if (nonRequestable != 0) { - return capabilityNameOf(BitUtils.unpackBits(nonRequestable)[0]); + return capabilityNameOf(NetworkCapabilitiesUtils.unpackBits(nonRequestable)[0]); } if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth"; if (hasSignalStrength()) return "signalStrength"; @@ -946,7 +946,7 @@ public final class NetworkCapabilities implements Parcelable { */ @SystemApi @NonNull public @Transport int[] getTransportTypes() { - return BitUtils.unpackBits(mTransportTypes); + return NetworkCapabilitiesUtils.unpackBits(mTransportTypes); } /** @@ -956,7 +956,7 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public void setTransportTypes(@Transport int[] transportTypes) { - mTransportTypes = BitUtils.packBits(transportTypes); + mTransportTypes = NetworkCapabilitiesUtils.packBits(transportTypes); } /** @@ -1721,8 +1721,10 @@ public final class NetworkCapabilities implements Parcelable { long oldImmutableCapabilities = this.mNetworkCapabilities & mask; long newImmutableCapabilities = that.mNetworkCapabilities & mask; if (oldImmutableCapabilities != newImmutableCapabilities) { - String before = capabilityNamesOf(BitUtils.unpackBits(oldImmutableCapabilities)); - String after = capabilityNamesOf(BitUtils.unpackBits(newImmutableCapabilities)); + String before = capabilityNamesOf(NetworkCapabilitiesUtils.unpackBits( + oldImmutableCapabilities)); + String after = capabilityNamesOf(NetworkCapabilitiesUtils.unpackBits( + newImmutableCapabilities)); joiner.add(String.format("immutable capabilities changed: %s -> %s", before, after)); } diff --git a/framework/src/android/net/util/DnsUtils.java b/framework/src/android/net/util/DnsUtils.java index 7908353eed..3fe245edb9 100644 --- a/framework/src/android/net/util/DnsUtils.java +++ b/framework/src/android/net/util/DnsUtils.java @@ -29,8 +29,6 @@ import android.system.ErrnoException; import android.system.Os; import android.util.Log; -import com.android.internal.util.BitUtils; - import libcore.io.IoUtils; import java.io.FileDescriptor; @@ -332,7 +330,7 @@ public class DnsUtils { if (srcByte[i] == dstByte[i]) { continue; } - int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]); + int x = (srcByte[i] & 0xff) ^ (dstByte[i] & 0xff); return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24); // Java ints are 32 bits } return dstByte.length * CHAR_BIT; From c5e54d4c37f0f7081bdafcb66517a948ca9388e2 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Sat, 6 Mar 2021 00:43:09 +0900 Subject: [PATCH 068/232] Move OemNetworkPreferences to Connectivity The data class supports a ConnectivityManager API, so it should be together with the ConnectivityManager API surface. Bug: 181512874 Test: m Change-Id: I5642486ea0febcb08cadcbd4cd3f0c6056deae0e --- .../android/net/OemNetworkPreferences.aidl | 19 ++ framework/api/system-current.txt | 20 ++ .../android/net/OemNetworkPreferences.java | 239 ++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 framework/aidl-export/android/net/OemNetworkPreferences.aidl create mode 100644 framework/src/android/net/OemNetworkPreferences.java diff --git a/framework/aidl-export/android/net/OemNetworkPreferences.aidl b/framework/aidl-export/android/net/OemNetworkPreferences.aidl new file mode 100644 index 0000000000..2b6a4ceef5 --- /dev/null +++ b/framework/aidl-export/android/net/OemNetworkPreferences.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.net; + +parcelable OemNetworkPreferences; diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index f5972fa340..a732430e6a 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -320,6 +320,26 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); } + public final class OemNetworkPreferences implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Map getNetworkPreferences(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4 + field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0 + } + + public static final class OemNetworkPreferences.Builder { + ctor public OemNetworkPreferences.Builder(); + ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences); + method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int); + method @NonNull public android.net.OemNetworkPreferences build(); + method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String); + } + public abstract class QosCallback { ctor public QosCallback(); method public void onError(@NonNull android.net.QosCallbackException); diff --git a/framework/src/android/net/OemNetworkPreferences.java b/framework/src/android/net/OemNetworkPreferences.java new file mode 100644 index 0000000000..48bd29769f --- /dev/null +++ b/framework/src/android/net/OemNetworkPreferences.java @@ -0,0 +1,239 @@ +/* + * 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 android.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Network preferences to set the default active network on a per-application basis as per a given + * {@link OemNetworkPreference}. An example of this would be to set an application's network + * preference to {@link #OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK} which would have the default + * network for that application set to an unmetered network first if available and if not, it then + * set that application's default network to an OEM managed network if available. + * + * @hide + */ +@SystemApi +public final class OemNetworkPreferences implements Parcelable { + /** + * Default in case this value is not set. Using it will result in an error. + */ + public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; + + /** + * If an unmetered network is available, use it. + * Otherwise, if a network with the OEM_PAID capability is available, use it. + * Otherwise, use the general default network. + */ + public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; + + /** + * If an unmetered network is available, use it. + * Otherwise, if a network with the OEM_PAID capability is available, use it. + * Otherwise, the app doesn't get a default network. + */ + public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; + + /** + * Use only NET_CAPABILITY_OEM_PAID networks. + */ + public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; + + /** + * Use only NET_CAPABILITY_OEM_PRIVATE networks. + */ + public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; + + @NonNull + private final Bundle mNetworkMappings; + + /** + * Return the currently built application package name to {@link OemNetworkPreference} mappings. + * @return the current network preferences map. + */ + @NonNull + public Map getNetworkPreferences() { + return convertToUnmodifiableMap(mNetworkMappings); + } + + private OemNetworkPreferences(@NonNull final Bundle networkMappings) { + Objects.requireNonNull(networkMappings); + mNetworkMappings = (Bundle) networkMappings.clone(); + } + + @Override + public String toString() { + return "OemNetworkPreferences{" + "mNetworkMappings=" + mNetworkMappings + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OemNetworkPreferences that = (OemNetworkPreferences) o; + + return mNetworkMappings.size() == that.mNetworkMappings.size() + && mNetworkMappings.toString().equals(that.mNetworkMappings.toString()); + } + + @Override + public int hashCode() { + return Objects.hash(mNetworkMappings); + } + + /** + * Builder used to create {@link OemNetworkPreferences} objects. Specify the preferred Network + * to package name mappings. + */ + public static final class Builder { + private final Bundle mNetworkMappings; + + public Builder() { + mNetworkMappings = new Bundle(); + } + + /** + * Constructor to populate the builder's values with an already built + * {@link OemNetworkPreferences}. + * @param preferences the {@link OemNetworkPreferences} to populate with. + */ + public Builder(@NonNull final OemNetworkPreferences preferences) { + Objects.requireNonNull(preferences); + mNetworkMappings = (Bundle) preferences.mNetworkMappings.clone(); + } + + /** + * Add a network preference for a given package. Previously stored values for the given + * package will be overwritten. + * + * @param packageName full package name (e.g.: "com.google.apps.contacts") of the app + * to use the given preference + * @param preference the desired network preference to use + * @return The builder to facilitate chaining. + */ + @NonNull + public Builder addNetworkPreference(@NonNull final String packageName, + @OemNetworkPreference final int preference) { + Objects.requireNonNull(packageName); + mNetworkMappings.putInt(packageName, preference); + return this; + } + + /** + * Remove a network preference for a given package. + * + * @param packageName full package name (e.g.: "com.google.apps.contacts") of the app to + * remove a preference for. + * @return The builder to facilitate chaining. + */ + @NonNull + public Builder clearNetworkPreference(@NonNull final String packageName) { + Objects.requireNonNull(packageName); + mNetworkMappings.remove(packageName); + return this; + } + + /** + * Build {@link OemNetworkPreferences} return the current OEM network preferences. + */ + @NonNull + public OemNetworkPreferences build() { + return new OemNetworkPreferences(mNetworkMappings); + } + } + + private static Map convertToUnmodifiableMap(@NonNull final Bundle bundle) { + final Map networkPreferences = new HashMap<>(); + for (final String key : bundle.keySet()) { + networkPreferences.put(key, bundle.getInt(key)); + } + return Collections.unmodifiableMap(networkPreferences); + } + + /** @hide */ + @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = { + OEM_NETWORK_PREFERENCE_UNINITIALIZED, + OEM_NETWORK_PREFERENCE_OEM_PAID, + OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK, + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY, + OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface OemNetworkPreference {} + + /** + * Return the string value for OemNetworkPreference + * + * @param value int value of OemNetworkPreference + * @return string version of OemNetworkPreference + * + * @hide + */ + @NonNull + public static String oemNetworkPreferenceToString(@OemNetworkPreference int value) { + switch (value) { + case OEM_NETWORK_PREFERENCE_UNINITIALIZED: + return "OEM_NETWORK_PREFERENCE_UNINITIALIZED"; + case OEM_NETWORK_PREFERENCE_OEM_PAID: + return "OEM_NETWORK_PREFERENCE_OEM_PAID"; + case OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK: + return "OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK"; + case OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY: + return "OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY"; + case OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY: + return "OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY"; + default: + return Integer.toHexString(value); + } + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + dest.writeBundle(mNetworkMappings); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public OemNetworkPreferences[] newArray(int size) { + return new OemNetworkPreferences[size]; + } + + @Override + public OemNetworkPreferences createFromParcel(@NonNull android.os.Parcel in) { + return new OemNetworkPreferences( + in.readBundle(getClass().getClassLoader())); + } + }; +} From a20cf48c1b3c885b16d3880ea41332a26ea753fa Mon Sep 17 00:00:00 2001 From: paulhu Date: Thu, 4 Mar 2021 10:53:27 +0800 Subject: [PATCH 069/232] Replace Inet[4|6]Address#ANY Connectivity is becoming a mainline module in S but mainline modules are not allowed to use non-formal APIs. Thus, replace non-formal API Inet[4|6]Address#ANY to NetworkStackConstants#IPV[4|6]_ADDR_ANY. Bug: 181756157 Test: FrameworksNetTests Change-Id: Id4d2fc551c1384f549a586e87ab68356ba05b995 --- framework/src/android/net/RouteInfo.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/framework/src/android/net/RouteInfo.java b/framework/src/android/net/RouteInfo.java index 5b6684ace0..fad3144a4b 100644 --- a/framework/src/android/net/RouteInfo.java +++ b/framework/src/android/net/RouteInfo.java @@ -26,6 +26,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.net.module.util.NetUtils; +import com.android.net.module.util.NetworkStackConstants; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -181,9 +182,9 @@ public final class RouteInfo implements Parcelable { if (destination == null) { if (gateway != null) { if (gateway instanceof Inet4Address) { - destination = new IpPrefix(Inet4Address.ANY, 0); + destination = new IpPrefix(NetworkStackConstants.IPV4_ADDR_ANY, 0); } else { - destination = new IpPrefix(Inet6Address.ANY, 0); + destination = new IpPrefix(NetworkStackConstants.IPV6_ADDR_ANY, 0); } } else { // no destination, no gateway. invalid. @@ -196,9 +197,9 @@ public final class RouteInfo implements Parcelable { // ConnectivityService) to stop doing things like r.getGateway().equals(), ... . if (gateway == null) { if (destination.getAddress() instanceof Inet4Address) { - gateway = Inet4Address.ANY; + gateway = NetworkStackConstants.IPV4_ADDR_ANY; } else { - gateway = Inet6Address.ANY; + gateway = NetworkStackConstants.IPV6_ADDR_ANY; } } mHasGateway = (!gateway.isAnyLocalAddress()); From a3b96dc5818fd48926566563ad328213fd7bbbbe Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Mon, 22 Feb 2021 21:03:44 +0800 Subject: [PATCH 070/232] [Telephony] Use TelephonyCallback instead of PhoneStateListener part1 Since the redesign of PhoneStateListener, use TelephonyCallback to get the callback of EVENT_* Bug: 167684594 Test: make Change-Id: Ia3b777b12142b104b5798804f50b34748f9bf28c Merged-In: Ia3b777b12142b104b5798804f50b34748f9bf28c --- .../android/net/util/MultinetworkPolicyTracker.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java index 43fffd733e..739ddada50 100644 --- a/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -30,8 +30,8 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.provider.Settings; -import android.telephony.PhoneStateListener; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.Log; @@ -92,8 +92,8 @@ public class MultinetworkPolicyTracker { } @VisibleForTesting - protected class ActiveDataSubscriptionIdChangedListener extends PhoneStateListener - implements PhoneStateListener.ActiveDataSubscriptionIdChangedListener { + protected class ActiveDataSubscriptionIdListener extends TelephonyCallback + implements TelephonyCallback.ActiveDataSubscriptionIdListener { @Override public void onActiveDataSubscriptionIdChanged(int subId) { mActiveSubId = subId; @@ -121,8 +121,8 @@ public class MultinetworkPolicyTracker { } }; - ctx.getSystemService(TelephonyManager.class).registerPhoneStateListener( - new HandlerExecutor(handler), new ActiveDataSubscriptionIdChangedListener()); + ctx.getSystemService(TelephonyManager.class).registerTelephonyCallback( + new HandlerExecutor(handler), new ActiveDataSubscriptionIdListener()); updateAvoidBadWifi(); updateMeteredMultipathPreference(); From 61c0b1a6f5f342db1a1971806075149fa31375b2 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 9 Mar 2021 23:53:55 +0000 Subject: [PATCH 071/232] Use ParcelDescriptor.fromSocket instead of getFileDescriptor Socket.getFileDescriptor$ is a hidden API. Instead, replace it with ParcelDescriptor.fromSocket, which was created to handle such use-cases. Bug: 170598012 Change-Id: I9e218e4ec29d2b7fe5d2faeb1c2e1cafc63dc923 Test: m --- framework/src/android/net/QosSocketInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java index d37c4691dd..3bf700b4d2 100644 --- a/framework/src/android/net/QosSocketInfo.java +++ b/framework/src/android/net/QosSocketInfo.java @@ -92,7 +92,7 @@ public final class QosSocketInfo implements Parcelable { Objects.requireNonNull(socket, "socket cannot be null"); mNetwork = Objects.requireNonNull(network, "network cannot be null"); - mParcelFileDescriptor = ParcelFileDescriptor.dup(socket.getFileDescriptor$()); + mParcelFileDescriptor = ParcelFileDescriptor.fromSocket(socket); mLocalSocketAddress = new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort()); } From 63338848da3044989c4ce1fe671a0a0c6f32f3ca Mon Sep 17 00:00:00 2001 From: Aaron Huang Date: Fri, 8 Jan 2021 18:32:00 +0800 Subject: [PATCH 072/232] Create a service-connectivity-pre-jarjar library This is needed for FrameworksNetTests because it inculdes service-connectivity. Without this library, the service-connectivity is already jarjar-ed which will cause the util classes couldn't be found when running the tests. So let the tests inculde the pre-jarjar version and service-connectivity applies the jarjar to this library. Bug: 177046265 Test: FrameworksNetTests Change-Id: I1acd95ff9bec99b918646e8ec3a57f3ef156e2ca Merged-In: I1acd95ff9bec99b918646e8ec3a57f3ef156e2ca --- service/Android.bp | 18 +++++++++++++++--- service/jarjar-rules.txt | 13 ++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/service/Android.bp b/service/Android.bp index e65b7b423b..2fb9f72fea 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -50,12 +50,11 @@ cc_library_shared { } java_library { - name: "service-connectivity", + name: "service-connectivity-pre-jarjar", srcs: [ + ":framework-connectivity-shared-srcs", ":connectivity-service-srcs", ], - installable: true, - jarjar_rules: "jarjar-rules.txt", libs: [ "android.net.ipsec.ike", "services.core", @@ -73,3 +72,16 @@ java_library { "com.android.tethering", ], } + +java_library { + name: "service-connectivity", + installable: true, + static_libs:[ + "service-connectivity-pre-jarjar", + ], + jarjar_rules: "jarjar-rules.txt", + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], +} diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt index d8205bf780..d8c60a428e 100644 --- a/service/jarjar-rules.txt +++ b/service/jarjar-rules.txt @@ -1,2 +1,13 @@ rule com.android.net.module.util.** com.android.connectivity.net-utils.@1 -rule com.android.modules.utils.** com.android.connectivity.modules-utils.@1 \ No newline at end of file +rule com.android.modules.utils.** com.android.connectivity.modules-utils.@1 + +# internal util classes +# Exclude AsyncChannel. TODO: remove AsyncChannel usage in ConnectivityService +rule com.android.internal.util.AsyncChannel* @0 +# Exclude LocationPermissionChecker. This is going to be moved to libs/net +rule com.android.internal.util.LocationPermissionChecker* @0 +rule android.util.LocalLog* com.android.connectivity.util.LocalLog@1 +# android.util.IndentingPrintWriter* should use a different package name from +# the one in com.android.internal.util +rule android.util.IndentingPrintWriter* android.connectivity.util.IndentingPrintWriter@1 +rule com.android.internal.util.** com.android.connectivity.util.@1 From 07051bcd834ce0325134b6518d80bb7569d5e821 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 11 Mar 2021 07:43:37 +0000 Subject: [PATCH 073/232] Remove usage of hidden InetSocketAddress constructor The constructor is a hidden API, and used in a code path that can never happen. Replace it with a thrown exception (which should never be thrown either). Bug: 170598012 Change-Id: Ie2c671c1a75accb8e94b08de9901d14b72caaf7e Test: m --- framework/src/android/net/QosSocketInfo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java index d37c4691dd..c345a9d77d 100644 --- a/framework/src/android/net/QosSocketInfo.java +++ b/framework/src/android/net/QosSocketInfo.java @@ -114,10 +114,10 @@ public final class QosSocketInfo implements Parcelable { try { return new InetSocketAddress(InetAddress.getByAddress(address), port); } catch (final UnknownHostException e) { - /* The catch block was purposely left empty. UnknownHostException will never be thrown + /* This can never happen. UnknownHostException will never be thrown since the address provided is numeric and non-null. */ + throw new RuntimeException("UnknownHostException on numeric address", e); } - return new InetSocketAddress(); } @Override From 5a4c7353b24a33de222dfc01eb5adae2ff861e35 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 11 Mar 2021 17:19:18 +0900 Subject: [PATCH 074/232] Move ParseException to Connectivity ParseException is a public API class used to support Connectivity APIs, so it should be in the same API surface as connectivity. Bug: 181512874 Test: m Change-Id: Ie1213de0d0facc8f409f7b4c2553abb382e4afbf --- framework/api/current.txt | 4 ++ framework/src/android/net/ParseException.java | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 framework/src/android/net/ParseException.java diff --git a/framework/api/current.txt b/framework/api/current.txt index a8f1a4d2a7..ab5d969f5d 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -401,6 +401,10 @@ package android.net { method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); } + public class ParseException extends java.lang.RuntimeException { + field public String response; + } + public class ProxyInfo implements android.os.Parcelable { ctor public ProxyInfo(@Nullable android.net.ProxyInfo); method public static android.net.ProxyInfo buildDirectProxy(String, int); diff --git a/framework/src/android/net/ParseException.java b/framework/src/android/net/ParseException.java new file mode 100644 index 0000000000..bcfdd7ef09 --- /dev/null +++ b/framework/src/android/net/ParseException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2006 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; + +import android.annotation.NonNull; + +/** + * Thrown when parsing failed. + */ +// See non-public class {@link WebAddress}. +public class ParseException extends RuntimeException { + public String response; + + ParseException(@NonNull String response) { + super(response); + this.response = response; + } + + ParseException(@NonNull String response, @NonNull Throwable cause) { + super(response, cause); + this.response = response; + } +} From a29be5c0f74c66507e26af85a09a22b60690c258 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 11 Mar 2021 10:56:49 +0000 Subject: [PATCH 075/232] Remove hidden INVALID_RESOURCE_ID in unused param INVALID_RESOURCE_ID is a hidden API so its usage should be avoided. The current usage is for an unused parameter, so just use a literal instead. Bug: 182451544 Change-Id: I066d9c34f735434adee4ee72e8a7fe1ceb900c3c Test: m --- framework/src/android/net/ConnectivityManager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 45ed3179d7..77e67bac8a 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -16,7 +16,6 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; -import static android.net.IpSecManager.INVALID_RESOURCE_ID; import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.REQUEST; @@ -1996,7 +1995,7 @@ public class ConnectivityManager { dup = createInvalidFd(); } return new NattSocketKeepalive(mService, network, dup, - INVALID_RESOURCE_ID /* Unused */, source, destination, executor, callback); + -1 /* Unused */, source, destination, executor, callback); } /** From 2d4413f8e3ecb83a46f3615ae7ae795df83fc9fa Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 11 Mar 2021 20:29:10 +0900 Subject: [PATCH 076/232] Remove hidden @NetworkType in NetworkInfo The NetworkType annotation is a hidden telephony symbol, and should be kept hidden as annotations are disallowed by API guidelines. Remove its usage in NetworkInfo as users of annotated constants that build against API stubs are expected not to use the annotation. Bug: 182451544 Test: m Change-Id: I6658c1faa147c527c989b87d67f1af166c488dde --- framework/src/android/net/NetworkInfo.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/src/android/net/NetworkInfo.java b/framework/src/android/net/NetworkInfo.java index d752901e2e..bb23494593 100644 --- a/framework/src/android/net/NetworkInfo.java +++ b/framework/src/android/net/NetworkInfo.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Annotation.NetworkType; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; @@ -164,7 +163,7 @@ public class NetworkInfo implements Parcelable { * @param typeName a human-readable string for the network type, or an empty string or null. * @param subtypeName a human-readable string for the subtype, or an empty string or null. */ - public NetworkInfo(int type, @NetworkType int subtype, + public NetworkInfo(int type, int subtype, @Nullable String typeName, @Nullable String subtypeName) { if (!ConnectivityManager.isNetworkTypeValid(type) && type != ConnectivityManager.TYPE_NONE) { From fa5eacc89f19bf96ed7de600a1a81b90454132f7 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 11 Mar 2021 20:49:13 +0900 Subject: [PATCH 077/232] Remove connectivity dependency on Preconditions Preconditions.checkNotNull is deprecated to be replaced by Objects.requireNonNull, and other methods can easily be replaced by inline checks. Preconditions is an internal API class that should not be used by unbundled jars. Bug: 177046265 Test: m Change-Id: If14a75439ff332c927dc4114ae0eecb89f53c6c7 --- .../net/ConnectivityDiagnosticsManager.java | 5 +-- .../src/android/net/ConnectivityManager.java | 44 +++++++++++-------- framework/src/android/net/MacAddress.java | 10 ++--- .../src/android/net/NetworkCapabilities.java | 11 ++--- .../android/net/StaticIpConfiguration.java | 3 +- .../src/android/net/TestNetworkManager.java | 7 ++- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.java b/framework/src/android/net/ConnectivityDiagnosticsManager.java index 5234494973..3598ebc701 100644 --- a/framework/src/android/net/ConnectivityDiagnosticsManager.java +++ b/framework/src/android/net/ConnectivityDiagnosticsManager.java @@ -28,7 +28,6 @@ import android.os.PersistableBundle; import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -70,8 +69,8 @@ public class ConnectivityDiagnosticsManager { /** @hide */ public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) { - mContext = Preconditions.checkNotNull(context, "missing context"); - mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + mContext = Objects.requireNonNull(context, "missing context"); + mService = Objects.requireNonNull(service, "missing IConnectivityManager"); } /** @hide */ diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 45ed3179d7..e463a7cd31 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -69,7 +69,6 @@ import android.util.SparseIntArray; import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; import com.android.internal.util.Protocol; import libcore.net.event.NetworkEventDispatcher; @@ -1733,7 +1732,9 @@ public class ConnectivityManager { // Map from type to transports. final int NOT_FOUND = -1; final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND); - Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type); + if (transport == NOT_FOUND) { + throw new IllegalArgumentException("unknown legacy type: " + type); + } nc.addTransportType(transport); // Map from type to capabilities. @@ -1838,8 +1839,8 @@ public class ConnectivityManager { } private PacketKeepalive(Network network, PacketKeepaliveCallback callback) { - Preconditions.checkNotNull(network, "network cannot be null"); - Preconditions.checkNotNull(callback, "callback cannot be null"); + Objects.requireNonNull(network, "network cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); mNetwork = network; mExecutor = Executors.newSingleThreadExecutor(); mCallback = new ISocketKeepaliveCallback.Stub() { @@ -2214,7 +2215,9 @@ public class ConnectivityManager { */ public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) { INetworkActivityListener rl = mNetworkActivityListeners.get(l); - Preconditions.checkArgument(rl != null, "Listener was not registered."); + if (rl == null) { + throw new IllegalArgumentException("Listener was not registered."); + } try { mService.registerNetworkActivityListener(rl); } catch (RemoteException e) { @@ -2242,8 +2245,8 @@ public class ConnectivityManager { * {@hide} */ public ConnectivityManager(Context context, IConnectivityManager service) { - mContext = Preconditions.checkNotNull(context, "missing context"); - mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + mContext = Objects.requireNonNull(context, "missing context"); + mService = Objects.requireNonNull(service, "missing IConnectivityManager"); mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE); sInstance = this; } @@ -2510,7 +2513,7 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int type, boolean showProvisioningUi, final OnStartTetheringCallback callback, Handler handler) { - Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null."); + Objects.requireNonNull(callback, "OnStartTetheringCallback cannot be null."); final Executor executor = new Executor() { @Override @@ -2603,7 +2606,7 @@ public class ConnectivityManager { public void registerTetheringEventCallback( @NonNull @CallbackExecutor Executor executor, @NonNull final OnTetheringEventCallback callback) { - Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null."); + Objects.requireNonNull(callback, "OnTetheringEventCallback cannot be null."); final TetheringEventCallback tetherCallback = new TetheringEventCallback() { @@ -2901,7 +2904,7 @@ public class ConnectivityManager { public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi, @NonNull @CallbackExecutor Executor executor, @NonNull final OnTetheringEntitlementResultListener listener) { - Preconditions.checkNotNull(listener, "TetheringEntitlementResultListener cannot be null."); + Objects.requireNonNull(listener, "TetheringEntitlementResultListener cannot be null."); ResultReceiver wrappedListener = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -3525,7 +3528,7 @@ public class ConnectivityManager { } CallbackHandler(Handler handler) { - this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper()); + this(Objects.requireNonNull(handler, "Handler cannot be null.").getLooper()); } @Override @@ -3623,9 +3626,9 @@ public class ConnectivityManager { int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { printStackTrace(); checkCallbackNotNull(callback); - Preconditions.checkArgument( - reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null, - "null NetworkCapabilities"); + if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) { + throw new IllegalArgumentException("null NetworkCapabilities"); + } final NetworkRequest request; final String callingPackageName = mContext.getOpPackageName(); try { @@ -3971,15 +3974,17 @@ public class ConnectivityManager { } private static void checkPendingIntentNotNull(PendingIntent intent) { - Preconditions.checkNotNull(intent, "PendingIntent cannot be null."); + Objects.requireNonNull(intent, "PendingIntent cannot be null."); } private static void checkCallbackNotNull(NetworkCallback callback) { - Preconditions.checkNotNull(callback, "null NetworkCallback"); + Objects.requireNonNull(callback, "null NetworkCallback"); } private static void checkTimeout(int timeoutMs) { - Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive."); + if (timeoutMs <= 0) { + throw new IllegalArgumentException("timeoutMs must be strictly positive."); + } } /** @@ -4229,8 +4234,9 @@ public class ConnectivityManager { // Find all requests associated to this callback and stop callback triggers immediately. // Callback is reusable immediately. http://b/20701525, http://b/35921499. synchronized (sCallbacks) { - Preconditions.checkArgument(networkCallback.networkRequest != null, - "NetworkCallback was not registered"); + if (networkCallback.networkRequest == null) { + throw new IllegalArgumentException("NetworkCallback was not registered"); + } if (networkCallback.networkRequest == ALREADY_UNREGISTERED) { Log.d(TAG, "NetworkCallback was already unregistered"); return; diff --git a/framework/src/android/net/MacAddress.java b/framework/src/android/net/MacAddress.java index c83c23a4b6..26a504a29c 100644 --- a/framework/src/android/net/MacAddress.java +++ b/framework/src/android/net/MacAddress.java @@ -25,7 +25,6 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.util.Preconditions; import com.android.net.module.util.MacAddressUtils; import java.lang.annotation.Retention; @@ -34,6 +33,7 @@ import java.net.Inet6Address; import java.net.UnknownHostException; import java.security.SecureRandom; import java.util.Arrays; +import java.util.Objects; /** * Representation of a MAC address. @@ -229,7 +229,7 @@ public final class MacAddress implements Parcelable { * @hide */ public static @NonNull byte[] byteAddrFromStringAddr(String addr) { - Preconditions.checkNotNull(addr); + Objects.requireNonNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { throw new IllegalArgumentException(addr + " was not a valid MAC address"); @@ -275,7 +275,7 @@ public final class MacAddress implements Parcelable { // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr)) // that avoids the allocation of an intermediary byte[]. private static long longAddrFromStringAddr(String addr) { - Preconditions.checkNotNull(addr); + Objects.requireNonNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { throw new IllegalArgumentException(addr + " was not a valid MAC address"); @@ -364,8 +364,8 @@ public final class MacAddress implements Parcelable { * */ public boolean matches(@NonNull MacAddress baseAddress, @NonNull MacAddress mask) { - Preconditions.checkNotNull(baseAddress); - Preconditions.checkNotNull(mask); + Objects.requireNonNull(baseAddress); + Objects.requireNonNull(mask); return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr); } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index c82cd3b4f3..7a693444e6 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -34,7 +34,6 @@ import android.util.ArraySet; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkCapabilitiesUtils; @@ -2073,8 +2072,9 @@ public final class NetworkCapabilities implements Parcelable { } private static void checkValidTransportType(@Transport int transport) { - Preconditions.checkArgument( - isValidTransport(transport), "Invalid TransportType " + transport); + if (!isValidTransport(transport)) { + throw new IllegalArgumentException("Invalid TransportType " + transport); + } } private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) { @@ -2082,8 +2082,9 @@ public final class NetworkCapabilities implements Parcelable { } private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) { - Preconditions.checkArgument(isValidCapability(capability), - "NetworkCapability " + capability + "out of range"); + if (!isValidCapability(capability)) { + throw new IllegalArgumentException("NetworkCapability " + capability + "out of range"); + } } /** diff --git a/framework/src/android/net/StaticIpConfiguration.java b/framework/src/android/net/StaticIpConfiguration.java index ce545974f5..7904f7a4ec 100644 --- a/framework/src/android/net/StaticIpConfiguration.java +++ b/framework/src/android/net/StaticIpConfiguration.java @@ -24,7 +24,6 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.util.Preconditions; import com.android.net.module.util.InetAddressUtils; import java.net.InetAddress; @@ -153,7 +152,7 @@ public final class StaticIpConfiguration implements Parcelable { * @return The {@link Builder} for chaining. */ public @NonNull Builder setDnsServers(@NonNull Iterable dnsServers) { - Preconditions.checkNotNull(dnsServers); + Objects.requireNonNull(dnsServers); mDnsServers = dnsServers; return this; } diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java index a174a7be85..a7a62351e5 100644 --- a/framework/src/android/net/TestNetworkManager.java +++ b/framework/src/android/net/TestNetworkManager.java @@ -21,10 +21,9 @@ import android.annotation.SystemApi; import android.os.IBinder; import android.os.RemoteException; -import com.android.internal.util.Preconditions; - import java.util.Arrays; import java.util.Collection; +import java.util.Objects; /** * Class that allows creation and management of per-app, test-only networks @@ -50,7 +49,7 @@ public class TestNetworkManager { /** @hide */ public TestNetworkManager(@NonNull ITestNetworkManager service) { - mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager"); + mService = Objects.requireNonNull(service, "missing ITestNetworkManager"); } /** @@ -93,7 +92,7 @@ public class TestNetworkManager { */ public void setupTestNetwork( @NonNull LinkProperties lp, boolean isMetered, @NonNull IBinder binder) { - Preconditions.checkNotNull(lp, "Invalid LinkProperties"); + Objects.requireNonNull(lp, "Invalid LinkProperties"); setupTestNetwork(lp.getInterfaceName(), lp, isMetered, new int[0], binder); } From c7bf00636e2e39110497ac09dcb09ef216aa1b1e Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 11 Mar 2021 21:01:30 +0900 Subject: [PATCH 078/232] Remove Slog usage in NetworkState Slog should not be used in unbundled jars as it is a hidden API; use the standard Log utility instead. Bug: 172050541 Test: m Change-Id: I54b2b99b2aedbb5194e9ec24068d2f2ce46d67fc --- framework/src/android/net/NetworkState.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/NetworkState.java b/framework/src/android/net/NetworkState.java index d01026566c..9b69674728 100644 --- a/framework/src/android/net/NetworkState.java +++ b/framework/src/android/net/NetworkState.java @@ -22,7 +22,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import android.util.Slog; +import android.util.Log; /** * Snapshot of network state. @@ -83,7 +83,7 @@ public class NetworkState implements Parcelable { if (VALIDATE_ROAMING_STATE && networkInfo != null && networkCapabilities != null) { if (networkInfo.isRoaming() == networkCapabilities .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) { - Slog.wtf("NetworkState", "Roaming state disagreement between " + networkInfo + Log.wtf("NetworkState", "Roaming state disagreement between " + networkInfo + " and " + networkCapabilities); } } From 5a5c99b84e1c3af1e710684f75919c39b4198343 Mon Sep 17 00:00:00 2001 From: junyulai Date: Fri, 5 Mar 2021 15:51:17 +0800 Subject: [PATCH 079/232] [VCN10] Add new API to listen for highest score network Test: atest ConnectivityServiceTest#testRegisterBestMatchingNetworkCallback Bug: 175662146 Change-Id: Ifa411c7b53da789c74fff7e1a95f9c9ebf5bd05c --- framework/src/android/net/ConnectivityManager.java | 13 +++++++++++++ framework/src/android/net/NetworkRequest.java | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 45ed3179d7..3d1da91cf3 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -20,6 +20,7 @@ import static android.net.IpSecManager.INVALID_RESOURCE_ID; import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.REQUEST; +import static android.net.NetworkRequest.Type.TRACK_BEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; @@ -4189,6 +4190,18 @@ public class ConnectivityManager { TRACK_SYSTEM_DEFAULT, TYPE_NONE, cbHandler); } + /** + * @hide + */ + // TODO: Make it public api. + @SuppressLint("ExecutorRegistration") + public void registerBestMatchingNetworkCallback(@NonNull NetworkRequest request, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + final NetworkCapabilities nc = request.networkCapabilities; + final CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(nc, networkCallback, 0, TRACK_BEST, TYPE_NONE, cbHandler); + } + /** * Requests bandwidth update for a given {@link Network} and returns whether the update request * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index b4a651c060..59b539af39 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -113,6 +113,10 @@ public class NetworkRequest implements Parcelable { * for the network (if any) that satisfies the default Internet * request. * + * - TRACK_BEST, which causes the framework to send callbacks about + * the single, highest scoring current network (if any) that matches + * the specified NetworkCapabilities. + * * - BACKGROUND_REQUEST, like REQUEST but does not cause any networks * to retain the NET_CAPABILITY_FOREGROUND capability. A network with * no foreground requests is in the background. A network that has @@ -135,6 +139,7 @@ public class NetworkRequest implements Parcelable { REQUEST, BACKGROUND_REQUEST, TRACK_SYSTEM_DEFAULT, + TRACK_BEST, }; /** From dbb7046923f7a3752242e98800dc3574adaf8265 Mon Sep 17 00:00:00 2001 From: junyulai Date: Tue, 9 Mar 2021 20:49:48 +0800 Subject: [PATCH 080/232] [VCN11] Make requestBackgroundNetwork requires handler Test: atest FrameworksNetTests android.net.cts.ConnectivityManagerTest Bug: 175662146 Change-Id: Iac9487e8de8bfdd87fc7a0153b228ae2a7ba4e19 --- framework/api/module-lib-current.txt | 2 +- framework/src/android/net/ConnectivityManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index d2ed73ef82..65a44f05cc 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -8,7 +8,7 @@ package android.net { public class ConnectivityManager { method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshot(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); } diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 3d1da91cf3..adf22da5be 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4992,10 +4992,10 @@ public class ConnectivityManager { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK }) public void requestBackgroundNetwork(@NonNull NetworkRequest request, - @Nullable Handler handler, @NonNull NetworkCallback networkCallback) { + @NonNull Handler handler, @NonNull NetworkCallback networkCallback) { final NetworkCapabilities nc = request.networkCapabilities; sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, - TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler)); + TYPE_NONE, new CallbackHandler(handler)); } /** From ea33ac97f6ddc3d8350b4bdf030354c02309ce03 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 4 Feb 2021 14:20:19 +0900 Subject: [PATCH 081/232] Add Ethernet, TestNetworkSpecifier API Rename StringNetworkSpecifier to EthernetNetworkSpecifier (its only production user), and make it module-lib API. The original StringNetworkSpecifier file is actually kept to satisfy some invalid dependencies; it will be removed separately. This allows specifying an Ethernet interface with a non-deprecated API: until this change the only way to do so would be to use NetworkRequest#setSpecifier(String), which is deprecated. Similarly, add the TestNetworkSpecifier API for TestNetworkManager, to replace previous usage of StringNetworkSpecifier. TestNetworkManager is module API, so TestNetworkSpecifier should be module API too. This allows tests to request the test interface specifically, without using the deprecated NetworkRequest#setSpecifier(String). Bug: 179329291 Test: m Merged-In: Iee569f5c8bbdc4bc979610e1191308281f3d4620 Change-Id: Iee569f5c8bbdc4bc979610e1191308281f3d4620 --- framework/api/module-lib-current.txt | 8 ++ framework/src/android/net/NetworkRequest.java | 17 +++- .../src/android/net/TestNetworkSpecifier.java | 97 +++++++++++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 framework/src/android/net/TestNetworkSpecifier.java diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index d2ed73ef82..6df57c1323 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -51,6 +51,14 @@ package android.net { field public static final String TEST_TAP_PREFIX = "testtap"; } + public final class TestNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { + ctor public TestNetworkSpecifier(@NonNull String); + method public int describeContents(); + method @Nullable public String getInterfaceName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { ctor public VpnTransportInfo(int); method public int describeContents(); diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index b4a651c060..17a8ee1720 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -31,6 +31,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVIT import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; import android.annotation.NonNull; import android.annotation.Nullable; @@ -382,11 +383,17 @@ public class NetworkRequest implements Parcelable { return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() .setSubscriptionId(subId).build()); } catch (NumberFormatException nfe) { - // A StringNetworkSpecifier does not accept null or empty ("") strings. When network - // specifiers were strings a null string and an empty string were considered - // equivalent. Hence no meaning is attached to a null or empty ("") string. - return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null - : new StringNetworkSpecifier(networkSpecifier)); + // An EthernetNetworkSpecifier or TestNetworkSpecifier does not accept null or empty + // ("") strings. When network specifiers were strings a null string and an empty + // string were considered equivalent. Hence no meaning is attached to a null or + // empty ("") string. + if (TextUtils.isEmpty(networkSpecifier)) { + return setNetworkSpecifier((NetworkSpecifier) null); + } else if (mNetworkCapabilities.hasTransport(TRANSPORT_TEST)) { + return setNetworkSpecifier(new TestNetworkSpecifier(networkSpecifier)); + } else { + return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier)); + } } } diff --git a/framework/src/android/net/TestNetworkSpecifier.java b/framework/src/android/net/TestNetworkSpecifier.java new file mode 100644 index 0000000000..b7470a591d --- /dev/null +++ b/framework/src/android/net/TestNetworkSpecifier.java @@ -0,0 +1,97 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * A {@link NetworkSpecifier} used to identify test interfaces. + * + * @see TestNetworkManager + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class TestNetworkSpecifier extends NetworkSpecifier implements Parcelable { + + /** + * Name of the network interface. + */ + @NonNull + private final String mInterfaceName; + + public TestNetworkSpecifier(@NonNull String interfaceName) { + Preconditions.checkStringNotEmpty(interfaceName); + mInterfaceName = interfaceName; + } + + // This may be null in the future to support specifiers based on data other than the interface + // name. + @Nullable + public String getInterfaceName() { + return mInterfaceName; + } + + @Override + public boolean canBeSatisfiedBy(@Nullable NetworkSpecifier other) { + return equals(other); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TestNetworkSpecifier)) return false; + return TextUtils.equals(mInterfaceName, ((TestNetworkSpecifier) o).mInterfaceName); + } + + @Override + public int hashCode() { + return Objects.hashCode(mInterfaceName); + } + + @Override + public String toString() { + return "TestNetworkSpecifier (" + mInterfaceName + ")"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mInterfaceName); + } + + public static final @NonNull Creator CREATOR = + new Creator() { + public TestNetworkSpecifier createFromParcel(Parcel in) { + return new TestNetworkSpecifier(in.readString()); + } + public TestNetworkSpecifier[] newArray(int size) { + return new TestNetworkSpecifier[size]; + } + }; +} From 697b459ad9e4347ea02ba1fa74c6b7484c2f38d0 Mon Sep 17 00:00:00 2001 From: junyulai Date: Thu, 24 Dec 2020 19:14:36 +0800 Subject: [PATCH 082/232] [VCN04] Add Subscription Id set into NetworkCapabilities This is a generic way to request networks that has different subId but belongs to the same carrier. For example, cellular networks with different SIM card, or carrier Wifi that provided by the operator. Test: atest NetworkCapabilitiesTest#testSubIds Test: m doc-comment-check-docs -j Test: atest CtsNetTestCases Bug: 175662146 Change-Id: Ifca766f5acc73c285948d6251ec31506d9bb0bcb --- .../src/android/net/NetworkCapabilities.java | 101 +++++++++++++++++- framework/src/android/net/NetworkRequest.java | 17 +++ 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index c82cd3b4f3..73fbd614cb 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -131,6 +131,7 @@ public final class NetworkCapabilities implements Parcelable { mPrivateDnsBroken = false; mRequestorUid = Process.INVALID_UID; mRequestorPackageName = null; + mSubIds = new ArraySet<>(); } /** @@ -159,6 +160,7 @@ public final class NetworkCapabilities implements Parcelable { mPrivateDnsBroken = nc.mPrivateDnsBroken; mRequestorUid = nc.mRequestorUid; mRequestorPackageName = nc.mRequestorPackageName; + mSubIds = new ArraySet<>(nc.mSubIds); } /** @@ -1655,6 +1657,7 @@ public final class NetworkCapabilities implements Parcelable { combineSSIDs(nc); combineRequestor(nc); combineAdministratorUids(nc); + combineSubIds(nc); } /** @@ -1674,8 +1677,9 @@ public final class NetworkCapabilities implements Parcelable { && satisfiedBySpecifier(nc) && (onlyImmutable || satisfiedBySignalStrength(nc)) && (onlyImmutable || satisfiedByUids(nc)) - && (onlyImmutable || satisfiedBySSID(nc))) - && (onlyImmutable || satisfiedByRequestor(nc)); + && (onlyImmutable || satisfiedBySSID(nc)) + && (onlyImmutable || satisfiedByRequestor(nc)) + && (onlyImmutable || satisfiedBySubIds(nc))); } /** @@ -1771,7 +1775,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsOwnerUid(that) && equalsPrivateDnsBroken(that) && equalsRequestor(that) - && equalsAdministratorUids(that); + && equalsAdministratorUids(that) + && equalsSubIds(that); } @Override @@ -1793,7 +1798,8 @@ public final class NetworkCapabilities implements Parcelable { + Objects.hashCode(mPrivateDnsBroken) * 47 + Objects.hashCode(mRequestorUid) * 53 + Objects.hashCode(mRequestorPackageName) * 59 - + Arrays.hashCode(mAdministratorUids) * 61; + + Arrays.hashCode(mAdministratorUids) * 61 + + Objects.hashCode(mSubIds) * 67; } @Override @@ -1827,6 +1833,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeInt(mOwnerUid); dest.writeInt(mRequestorUid); dest.writeString(mRequestorPackageName); + dest.writeIntArray(CollectionUtils.toIntArray(mSubIds)); } public static final @android.annotation.NonNull Creator CREATOR = @@ -1850,6 +1857,11 @@ public final class NetworkCapabilities implements Parcelable { netCap.mOwnerUid = in.readInt(); netCap.mRequestorUid = in.readInt(); netCap.mRequestorPackageName = in.readString(); + netCap.mSubIds = new ArraySet<>(); + final int[] subIdInts = Objects.requireNonNull(in.createIntArray()); + for (int i = 0; i < subIdInts.length; i++) { + netCap.mSubIds.add(subIdInts[i]); + } return netCap; } @Override @@ -1933,11 +1945,14 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" SSID: ").append(mSSID); } - if (mPrivateDnsBroken) { sb.append(" PrivateDnsBroken"); } + if (!mSubIds.isEmpty()) { + sb.append(" SubscriptionIds: ").append(mSubIds); + } + sb.append("]"); return sb.toString(); } @@ -2250,6 +2265,68 @@ public final class NetworkCapabilities implements Parcelable { && TextUtils.equals(mRequestorPackageName, nc.mRequestorPackageName); } + /** + * Set of the subscription IDs that identifies the network or request, empty if none. + */ + @NonNull + private ArraySet mSubIds = new ArraySet<>(); + + /** + * Sets the subscription ID set that associated to this network or request. + * + * @hide + */ + @NonNull + public NetworkCapabilities setSubIds(@NonNull Set subIds) { + mSubIds = new ArraySet(Objects.requireNonNull(subIds)); + return this; + } + + /** + * Gets the subscription ID set that associated to this network or request. + * @hide + * @return + */ + @NonNull + public Set getSubIds() { + return new ArraySet<>(mSubIds); + } + + /** + * Tests if the subscription ID set of this network is the same as that of the passed one. + */ + private boolean equalsSubIds(@NonNull NetworkCapabilities nc) { + return Objects.equals(mSubIds, nc.mSubIds); + } + + /** + * Check if the subscription ID set requirements of this object are matched by the passed one. + * If specified in the request, the passed one need to have at least one subId and at least + * one of them needs to be in the request set. + */ + private boolean satisfiedBySubIds(@NonNull NetworkCapabilities nc) { + if (mSubIds.isEmpty()) return true; + if (nc.mSubIds.isEmpty()) return false; + for (final Integer subId : nc.mSubIds) { + if (mSubIds.contains(subId)) return true; + } + return false; + } + + /** + * Combine subscription ID set of the capabilities. + * + *

    This is only legal if the subscription Ids are equal. + * + *

    If both subscription IDs are not equal, they belong to different subscription + * (or no subscription). In this case, it would not make sense to add them together. + */ + private void combineSubIds(@NonNull NetworkCapabilities nc) { + if (!Objects.equals(mSubIds, nc.mSubIds)) { + throw new IllegalStateException("Can't combine two subscription ID sets"); + } + } + /** * Builder class for NetworkCapabilities. * @@ -2555,6 +2632,20 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Set the subscription ID set. + * + * @param subIds a set that represent the subscription IDs. Empty if clean up. + * @return this builder. + * + * @hide + */ + @NonNull + public Builder setSubIds(@NonNull final Set subIds) { + mCaps.setSubIds(subIds); + return this; + } + /** * Builds the instance of the capabilities. * diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 17a8ee1720..65ca1b2f7d 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -456,6 +456,23 @@ public class NetworkRequest implements Parcelable { } nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); } + + /** + * Sets the optional subscription ID set. + *

    + * This specify the subscription IDs requirement. + * A network will satisfy this request only if it matches one of the subIds in this set. + * An empty set matches all networks, including those without a subId. + * + * @param subIds A {@code Set} that represents subscription IDs. + * + * @hide + */ + @NonNull + public Builder setSubIds(@NonNull Set subIds) { + mNetworkCapabilities.setSubIds(subIds); + return this; + } } // implement the Parcelable interface From e1b65bdbfe4aef6b4367d9a5c3346eadb4a105c7 Mon Sep 17 00:00:00 2001 From: junyulai Date: Thu, 11 Mar 2021 20:20:19 +0800 Subject: [PATCH 083/232] [VCN12] Expose setSubIds/getSubIds APIs Test: atest NetworkCapabilitiesTest Bug: 175662146 Change-Id: Ia4b98bc6c5fcefee44233f3b7fbb6517a0e8870e --- framework/api/current.txt | 2 ++ framework/api/system-current.txt | 1 + framework/src/android/net/NetworkCapabilities.java | 3 --- framework/src/android/net/NetworkRequest.java | 2 -- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index a8f1a4d2a7..61ffaca5df 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -293,6 +293,7 @@ package android.net { method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); method public int getOwnerUid(); method public int getSignalStrength(); + method @NonNull public java.util.Set getSubIds(); method @Nullable public android.net.TransportInfo getTransportInfo(); method public boolean hasCapability(int); method public boolean hasTransport(int); @@ -399,6 +400,7 @@ package android.net { method public android.net.NetworkRequest.Builder removeTransportType(int); method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); + method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set); } public class ProxyInfo implements android.os.Parcelable { diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index a732430e6a..a98f14ea94 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -296,6 +296,7 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); + method @NonNull public android.net.NetworkCapabilities.Builder setSubIds(@NonNull java.util.Set); method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 73fbd614cb..4e1fd1c8a7 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -2284,7 +2284,6 @@ public final class NetworkCapabilities implements Parcelable { /** * Gets the subscription ID set that associated to this network or request. - * @hide * @return */ @NonNull @@ -2637,8 +2636,6 @@ public final class NetworkCapabilities implements Parcelable { * * @param subIds a set that represent the subscription IDs. Empty if clean up. * @return this builder. - * - * @hide */ @NonNull public Builder setSubIds(@NonNull final Set subIds) { diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 65ca1b2f7d..960d967275 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -465,8 +465,6 @@ public class NetworkRequest implements Parcelable { * An empty set matches all networks, including those without a subId. * * @param subIds A {@code Set} that represents subscription IDs. - * - * @hide */ @NonNull public Builder setSubIds(@NonNull Set subIds) { From 5cdbcfb7faffa474689a23eeb28cf3bf2e5c52fb Mon Sep 17 00:00:00 2001 From: lucaslin Date: Fri, 12 Mar 2021 00:46:33 +0800 Subject: [PATCH 084/232] Add a new API to get the network ID range of IPSec tunnel interface - Add a new API to get the network ID range of IPSec tunnel interface. - Use the new API in IpSecServiceTest to make sure the result is the same. Follow-up commit will change the logic in IpSecService#reserveNetId(), the modified test can ensure the correctness of the new change. Bug: 172183305 Test: atest FrameworksNetTests:IpSecServiceTest Change-Id: Ic605e48941fc9d6482cdcd01a8adcdc9b6d586a6 --- framework/api/module-lib-current.txt | 1 + .../src/android/net/ConnectivityManager.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index d2ed73ef82..a55ed6f113 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -7,6 +7,7 @@ package android.net { public class ConnectivityManager { method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshot(); + method @NonNull public static android.util.Range getIpSecNetIdRange(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 45ed3179d7..a2fcdd64a0 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5040,4 +5040,21 @@ public class ConnectivityManager { throw e.rethrowFromSystemServer(); } } + + // The first network ID of IPSec tunnel interface. + private static final int TUN_INTF_NETID_START = 0xFC00; + // The network ID range of IPSec tunnel interface. + private static final int TUN_INTF_NETID_RANGE = 0x0400; + + /** + * Get the network ID range reserved for IPSec tunnel interfaces. + * + * @return A Range which indicates the network ID range of IPSec tunnel interface. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @NonNull + public static Range getIpSecNetIdRange() { + return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1); + } } From 686d5d2c3dd56f56326b02e09ee1ac90f44c9fda Mon Sep 17 00:00:00 2001 From: Daniel Bright Date: Wed, 10 Mar 2021 11:51:50 -0800 Subject: [PATCH 085/232] Swap parameters in QosCallback#registerQosCallback Bug: 181551395 Test: unit tests Change-Id: I9f3f7e7a1bbb19629a2607b82ce316768c4143df --- framework/api/system-current.txt | 2 +- framework/src/android/net/ConnectivityManager.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index a732430e6a..39b9c7d031 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -52,7 +52,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); - method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor); + method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.QosCallback); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index a6dc9ce051..34d95c7978 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4804,20 +4804,20 @@ public class ConnectivityManager { * {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}. * * @param socketInfo the socket information used to match QoS events - * @param callback receives qos events that satisfy socketInfo * @param executor The executor on which the callback will be invoked. The provided * {@link Executor} must run callback sequentially, otherwise the order of - * callbacks cannot be guaranteed. + * callbacks cannot be guaranteed.onQosCallbackRegistered + * @param callback receives qos events that satisfy socketInfo * * @hide */ @SystemApi public void registerQosCallback(@NonNull final QosSocketInfo socketInfo, - @NonNull final QosCallback callback, - @CallbackExecutor @NonNull final Executor executor) { + @CallbackExecutor @NonNull final Executor executor, + @NonNull final QosCallback callback) { Objects.requireNonNull(socketInfo, "socketInfo must be non-null"); - Objects.requireNonNull(callback, "callback must be non-null"); Objects.requireNonNull(executor, "executor must be non-null"); + Objects.requireNonNull(callback, "callback must be non-null"); try { synchronized (mQosCallbackConnections) { From e08bc1803cd04936aa8933d467a05066c432a2ed Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Tue, 22 Dec 2020 15:10:42 -0800 Subject: [PATCH 086/232] ConnectivityManager: Provide API's to include location sensitive info Existing NetworkCallback users will get NetworkCapabilities with location sensitive data removed (except for ownerUid which will be added for existing apps for backwards compatibility). Apps have to opt-in to receive location sensitive data. Note: This was chosen because WifiInfo is the only TransportInfo tha has location sensitive info & that was added only in Android 12. If we choose to default to true, all existings apps retrieving NetworkCapabilities for wifi networks will be blamed for location access unnecessarily. Changes: i) Add a flag in NetworkCallback creation to retrieve NetworkCapabilities with location sensitive info in their callback. (More flags are being planned for NetworkCallback for throttling callback frequency, etc) ii) For NetworkCapabilities.getOwnerUid(), we will continue to send the data for apps targeting older SDK (since this is an existing field and the new flag defaults location sensitive data to off). Bug: 156867433 Test: atest android.net Test: atest com.android.server Change-Id: If70b5ea6f5c8885f0c353c8df08a826d55fe7f7a --- framework/api/current.txt | 2 + .../src/android/net/ConnectivityManager.java | 83 ++++++++++++++++--- .../src/android/net/IConnectivityManager.aidl | 4 +- .../src/android/net/NetworkCapabilities.java | 11 +++ 4 files changed, 86 insertions(+), 14 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index a8f1a4d2a7..8a7ed47286 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -143,6 +143,7 @@ package android.net { public static class ConnectivityManager.NetworkCallback { ctor public ConnectivityManager.NetworkCallback(); + ctor public ConnectivityManager.NetworkCallback(int); method public void onAvailable(@NonNull android.net.Network); method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean); method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities); @@ -150,6 +151,7 @@ package android.net { method public void onLosing(@NonNull android.net.Network, int); method public void onLost(@NonNull android.net.Network); method public void onUnavailable(); + field public static final int FLAG_INCLUDE_LOCATION_INFO = 1; // 0x1 } public static interface ConnectivityManager.OnNetworkActiveListener { diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index adf22da5be..c160d82375 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -44,6 +44,7 @@ import android.net.SocketKeepalive.Callback; import android.net.TetheringManager.StartTetheringCallback; import android.net.TetheringManager.TetheringEventCallback; import android.net.TetheringManager.TetheringRequest; +import android.net.wifi.WifiNetworkSuggestion; import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; @@ -1315,7 +1316,7 @@ public class ConnectivityManager { } /** - * Returns an array of {@link android.net.NetworkCapabilities} objects, representing + * Returns an array of {@link NetworkCapabilities} objects, representing * the Networks that applications run by the given user will use by default. * @hide */ @@ -1395,11 +1396,19 @@ public class ConnectivityManager { } /** - * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This + * Get the {@link NetworkCapabilities} for the given {@link Network}. This * will return {@code null} if the network is unknown. * + * This will remove any location sensitive data in {@link TransportInfo} embedded in + * {@link NetworkCapabilities#getTransportInfo()}. Some transport info instances like + * {@link android.net.wifi.WifiInfo} contain location sensitive information. Retrieving + * this location sensitive information (subject to app's location permissions) will be + * noted by system. To include any location sensitive data in {@link TransportInfo}, + * use a {@link NetworkCallback} with + * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag. + * * @param network The {@link Network} object identifying the network in question. - * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}. + * @return The {@link NetworkCapabilities} for the network, or {@code null}. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable @@ -3244,6 +3253,54 @@ public class ConnectivityManager { * A {@code NetworkCallback} that has been unregistered can be registered again. */ public static class NetworkCallback { + /** + * No flags associated with this callback. + * @hide + */ + public static final int FLAG_NONE = 0; + /** + * Use this flag to include any location sensitive data in {@link NetworkCapabilities} sent + * via {@link #onCapabilitiesChanged(Network, NetworkCapabilities)}. + *

    + * These include: + *

  9. Some transport info instances (retrieved via + * {@link NetworkCapabilities#getTransportInfo()}) like {@link android.net.wifi.WifiInfo} + * contain location sensitive information. + *
  10. OwnerUid (retrieved via {@link NetworkCapabilities#getOwnerUid()} is location + * sensitive for wifi suggestor apps (i.e using {@link WifiNetworkSuggestion}).
  11. + *

    + *

    + * Note: + *

  12. Retrieving this location sensitive information (subject to app's location + * permissions) will be noted by system.
  13. + *
  14. Without this flag any {@link NetworkCapabilities} provided via the callback does + * not include location sensitive info. + *

    + */ + public static final int FLAG_INCLUDE_LOCATION_INFO = 1 << 0; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = "FLAG_", value = { + FLAG_NONE, + FLAG_INCLUDE_LOCATION_INFO + }) + public @interface Flag { } + + /** + * All the valid flags for error checking. + */ + private static final int VALID_FLAGS = FLAG_INCLUDE_LOCATION_INFO; + + public NetworkCallback() { + this(FLAG_NONE); + } + + public NetworkCallback(@Flag int flags) { + Preconditions.checkArgument((flags & VALID_FLAGS) == flags); + mFlags = flags; + } + /** * Called when the framework connects to a new network to evaluate whether it satisfies this * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable} @@ -3381,7 +3438,7 @@ public class ConnectivityManager { * calling these methods while in a callback may return an outdated or even a null object. * * @param network The {@link Network} whose capabilities have changed. - * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this + * @param networkCapabilities The new {@link NetworkCapabilities} for this * network. */ public void onCapabilitiesChanged(@NonNull Network network, @@ -3450,6 +3507,7 @@ public class ConnectivityManager { public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {} private NetworkRequest networkRequest; + private final int mFlags; } /** @@ -3639,14 +3697,15 @@ public class ConnectivityManager { } Messenger messenger = new Messenger(handler); Binder binder = new Binder(); + final int callbackFlags = callback.mFlags; if (reqType == LISTEN) { request = mService.listenForNetwork( - need, messenger, binder, callingPackageName, + need, messenger, binder, callbackFlags, callingPackageName, getAttributionTag()); } else { request = mService.requestNetwork( need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType, - callingPackageName, getAttributionTag()); + callbackFlags, callingPackageName, getAttributionTag()); } if (request != null) { sCallbacks.put(request, callback); @@ -3693,7 +3752,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * Request a network to satisfy a set of {@link NetworkCapabilities}. * *

    This method will attempt to find the best network that matches the passed * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the @@ -3777,7 +3836,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * Request a network to satisfy a set of {@link NetworkCapabilities}. * * This method behaves identically to {@link #requestNetwork(NetworkRequest, NetworkCallback)} * but runs all the callbacks on the passed Handler. @@ -3799,7 +3858,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * Request a network to satisfy a set of {@link NetworkCapabilities}, limited * by a timeout. * * This function behaves identically to the non-timed-out version @@ -3834,7 +3893,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * Request a network to satisfy a set of {@link NetworkCapabilities}, limited * by a timeout. * * This method behaves identically to @@ -3879,7 +3938,7 @@ public class ConnectivityManager { /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. + * Request a network to satisfy a set of {@link NetworkCapabilities}. * * This function behaves identically to the version that takes a NetworkCallback, but instead * of {@link NetworkCallback} a {@link PendingIntent} is used. This means @@ -4911,7 +4970,7 @@ public class ConnectivityManager { } /** - * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, but + * Request a network to satisfy a set of {@link NetworkCapabilities}, but * does not cause any networks to retain the NET_CAPABILITY_FOREGROUND capability. This can * be used to request that the system provide a network without causing the network to be * in the foreground. diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index cd49258d1c..f9393e315b 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -143,7 +143,7 @@ interface IConnectivityManager NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, - String callingPackageName, String callingAttributionTag); + int callbackFlags, String callingPackageName, String callingAttributionTag); NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities, in PendingIntent operation, String callingPackageName, String callingAttributionTag); @@ -151,7 +151,7 @@ interface IConnectivityManager void releasePendingNetworkRequest(in PendingIntent operation); NetworkRequest listenForNetwork(in NetworkCapabilities networkCapabilities, - in Messenger messenger, in IBinder binder, String callingPackageName, + in Messenger messenger, in IBinder binder, int callbackFlags, String callingPackageName, String callingAttributionTag); void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index c82cd3b4f3..7fe4794745 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -25,6 +25,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.ConnectivityManager.NetworkCallback; +import android.net.wifi.WifiNetworkSuggestion; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -1048,6 +1049,16 @@ public final class NetworkCapabilities implements Parcelable { * * Instances of NetworkCapabilities sent to apps without the appropriate permissions will have * this field cleared out. + * + *

    + * This field will only be populated for VPN and wifi network suggestor apps (i.e using + * {@link WifiNetworkSuggestion}), and only for the network they own. + * In the case of wifi network suggestors apps, this field is also location sensitive, so the + * app needs to hold {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission. If the + * app targets SDK version greater than or equal to {@link Build.VERSION_CODES#S}, then they + * also need to use {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} to get the info in their + * callback. The app will be blamed for location access if this field is included. + *

    */ public int getOwnerUid() { return mOwnerUid; From 907ebd4f90df2aa9ffc0044de5a777361c01f86b Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 8 Mar 2021 22:05:03 +0900 Subject: [PATCH 087/232] Create ServiceConnectivityResources Create the ServiceConnectivityResources package, which contains resources Connectivity unbundled from platform resources. Migrate the first few resources from ConnectivityService that have no RRO in AOSP. To avoid boot time impact, avoid loading the resources in the ConnectivityService constructor. Bug: 182125649 Test: atest FrameworksNetTests Merged-In: I77ac6f4303c54acc96f16e18ef02add30298ff3d Change-Id: I77ac6f4303c54acc96f16e18ef02add30298ff3d --- service/Android.bp | 1 + .../ServiceConnectivityResources/Android.bp | 35 +++++++++++++++ .../AndroidManifest.xml | 37 +++++++++++++++ .../res/values/config.xml | 45 +++++++++++++++++++ .../res/values/overlayable.xml | 27 +++++++++++ .../res/values/strings.xml | 22 +++++++++ 6 files changed, 167 insertions(+) create mode 100644 service/ServiceConnectivityResources/Android.bp create mode 100644 service/ServiceConnectivityResources/AndroidManifest.xml create mode 100644 service/ServiceConnectivityResources/res/values/config.xml create mode 100644 service/ServiceConnectivityResources/res/values/overlayable.xml create mode 100644 service/ServiceConnectivityResources/res/values/strings.xml diff --git a/service/Android.bp b/service/Android.bp index e65b7b423b..5392f8451a 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -61,6 +61,7 @@ java_library { "services.core", "services.net", "unsupportedappusage", + "ServiceConnectivityResources", ], static_libs: [ "modules-utils-os", diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp new file mode 100644 index 0000000000..f2446b7f7e --- /dev/null +++ b/service/ServiceConnectivityResources/Android.bp @@ -0,0 +1,35 @@ +// +// 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. +// + +// APK to hold all the wifi overlayable resources. +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_app { + name: "ServiceConnectivityResources", + sdk_version: "system_current", + resource_dirs: [ + "res", + ], + privileged: true, + export_package_resources: true, + apex_available: [ + "com.android.tethering", + ], + // TODO: use a dedicated cert once generated + certificate: "platform", +} diff --git a/service/ServiceConnectivityResources/AndroidManifest.xml b/service/ServiceConnectivityResources/AndroidManifest.xml new file mode 100644 index 0000000000..2c30302615 --- /dev/null +++ b/service/ServiceConnectivityResources/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml new file mode 100644 index 0000000000..7d98c76a40 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values/config.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + 60000 + + + + + + + \ No newline at end of file diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml new file mode 100644 index 0000000000..00ec2df0e6 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/service/ServiceConnectivityResources/res/values/strings.xml b/service/ServiceConnectivityResources/res/values/strings.xml new file mode 100644 index 0000000000..2c7b992650 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + + System Connectivity Resources + \ No newline at end of file From 9b47fcca83587f52d568a999fa3b550efe49e845 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 12 Mar 2021 18:30:30 +0900 Subject: [PATCH 088/232] Remove VpnType usage in VpnTransportInfo The VpnType annotation is a hidden symbol, and should be kept hidden as annotations are disallowed by API guidelines. Remove its usage in VpnTransportInfo as users of annotated constants that build against API stubs are expected not to use the annotation. Bug: 173331190 Test: m Change-Id: I171fa57f6279defad081c3cd16265d58ec55e57d --- framework/src/android/net/VpnTransportInfo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java index 340141b78a..c510079489 100644 --- a/framework/src/android/net/VpnTransportInfo.java +++ b/framework/src/android/net/VpnTransportInfo.java @@ -42,9 +42,9 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"}); /** Type of this VPN. */ - @VpnManager.VpnType public final int type; + public final int type; - public VpnTransportInfo(@VpnManager.VpnType int type) { + public VpnTransportInfo(int type) { this.type = type; } From 889dcda9c41bd84fe0d7350d850a6ff37cf57c5d Mon Sep 17 00:00:00 2001 From: lucaslin Date: Fri, 12 Mar 2021 17:56:09 +0800 Subject: [PATCH 089/232] Have a new API to get private DNS mode - Expose PRIVATE_DNS_MODE_OFF, PRIVATE_DNS_MODE_OPPORTUNISTIC and PRIVATE_DNS_MODE_PROVIDER_HOSTNAME for external users. - Since PRIVATE_DNS_DEFAULT_MODE_FALLBACK might be changed from release to release, so it cannot be exposed as a system API. Remove PRIVATE_DNS_DEFAULT_MODE_FALLBACK and have a new API - getPrivateDnsMode() for users to get the private DNS mode instead. Bug: 172183305 Test: atest FrameworksNetTests CtsNetTestCases Change-Id: I02a1e91b4eafb5f5df3eada1c07b99849a050c3c Merged-In: I02a1e91b4eafb5f5df3eada1c07b99849a050c3c --- framework/api/module-lib-current.txt | 4 ++ .../src/android/net/ConnectivityManager.java | 46 +++++++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 4b3336644e..a04571496c 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -8,9 +8,13 @@ package android.net { public class ConnectivityManager { method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshot(); method @NonNull public static android.util.Range getIpSecNetIdRange(); + method @NonNull public static String getPrivateDnsMode(@NonNull android.content.ContentResolver); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); + field public static final String PRIVATE_DNS_MODE_OFF = "off"; + field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; + field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; } public final class NetworkAgentConfig implements android.os.Parcelable { diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index e9107b67e4..d74efd5751 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -24,6 +24,8 @@ import static android.net.NetworkRequest.Type.TRACK_BEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; +import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE; +import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -32,11 +34,13 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.StringDef; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.IpSecManager.UdpEncapsulationSocket; @@ -64,6 +68,7 @@ import android.os.ServiceSpecificException; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Range; @@ -803,24 +808,27 @@ public class ConnectivityManager { /** * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public static final String PRIVATE_DNS_MODE_OFF = "off"; /** * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; /** * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; - /** - * The default Private DNS mode. - * - * This may change from release to release or may become dependent upon - * the capabilities of the underlying platform. - * - * @hide - */ - public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(value = { + PRIVATE_DNS_MODE_OFF, + PRIVATE_DNS_MODE_OPPORTUNISTIC, + PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, + }) + public @interface PrivateDnsMode {} @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; @@ -5129,4 +5137,24 @@ public class ConnectivityManager { public static Range getIpSecNetIdRange() { return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1); } + + /** + * Get private DNS mode from settings. + * + * @param cr The ContentResolver to query private DNS mode from settings. + * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @NonNull + @PrivateDnsMode + public static String getPrivateDnsMode(@NonNull ContentResolver cr) { + String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE); + if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE); + // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose + // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode. + if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC; + return mode; + } } From 0494b85ffb5e32ed6321c4af5dbb964a5c97ed7b Mon Sep 17 00:00:00 2001 From: paulhu Date: Wed, 3 Mar 2021 22:15:11 +0800 Subject: [PATCH 090/232] Replace InetAddress#parseNumericAddress -Connectivity is becoming a mainline module in S but mainline modules are not allowed to use non-formal APIs. Thus, replace non-formal API InetAddress#parseNumericAddress to InetAddresses#parseNumericAddress. - Add deprecated method legacyParseIpAndMask() for IpPrefix and LinkAddress. Because InetAddresses#parseNumericAddress has a little different behavior in some case, but these two classes should keep working as before. So these two classes will use the new deprecated method. Bug: 181756157 Test: FrameworksNetTests Change-Id: I1c96b75f0b8d5e93304a39b4a8c8849964e5e810 --- framework/src/android/net/IpPrefix.java | 2 +- framework/src/android/net/LinkAddress.java | 2 +- framework/src/android/net/NetworkUtils.java | 47 ++++++++++++++++++++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/framework/src/android/net/IpPrefix.java b/framework/src/android/net/IpPrefix.java index d2ee7d13b0..bf4481afc5 100644 --- a/framework/src/android/net/IpPrefix.java +++ b/framework/src/android/net/IpPrefix.java @@ -113,7 +113,7 @@ public final class IpPrefix implements Parcelable { // first statement in constructor". We could factor out setting the member variables to an // init() method, but if we did, then we'd have to make the members non-final, or "error: // cannot assign a value to final variable address". So we just duplicate the code here. - Pair ipAndMask = NetworkUtils.parseIpAndMask(prefix); + Pair ipAndMask = NetworkUtils.legacyParseIpAndMask(prefix); this.address = ipAndMask.first.getAddress(); this.prefixLength = ipAndMask.second; checkAndMaskAddressAndPrefixLength(); diff --git a/framework/src/android/net/LinkAddress.java b/framework/src/android/net/LinkAddress.java index d1bdaa078c..d48b8c71f4 100644 --- a/framework/src/android/net/LinkAddress.java +++ b/framework/src/android/net/LinkAddress.java @@ -325,7 +325,7 @@ public class LinkAddress implements Parcelable { public LinkAddress(@NonNull String address, int flags, int scope) { // This may throw an IllegalArgumentException; catching it is the caller's responsibility. // TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24". - Pair ipAndMask = NetworkUtils.parseIpAndMask(address); + Pair ipAndMask = NetworkUtils.legacyParseIpAndMask(address); init(ipAndMask.first, ipAndMask.second, flags, scope, LIFETIME_UNKNOWN, LIFETIME_UNKNOWN); } diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java index 9e42bbecbe..c0f262815b 100644 --- a/framework/src/android/net/NetworkUtils.java +++ b/framework/src/android/net/NetworkUtils.java @@ -27,8 +27,10 @@ import com.android.net.module.util.Inet4AddressUtils; import java.io.FileDescriptor; import java.math.BigInteger; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.SocketException; +import java.net.UnknownHostException; import java.util.Locale; import java.util.TreeSet; @@ -212,7 +214,7 @@ public class NetworkUtils { @Deprecated public static InetAddress numericToInetAddress(String addrString) throws IllegalArgumentException { - return InetAddress.parseNumericAddress(addrString); + return InetAddresses.parseNumericAddress(addrString); } /** @@ -234,7 +236,7 @@ public class NetworkUtils { try { String[] pieces = ipAndMaskString.split("/", 2); prefixLength = Integer.parseInt(pieces[1]); - address = InetAddress.parseNumericAddress(pieces[0]); + address = InetAddresses.parseNumericAddress(pieces[0]); } catch (NullPointerException e) { // Null string. } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. } catch (NumberFormatException e) { // Non-numeric prefix. @@ -248,6 +250,47 @@ public class NetworkUtils { return new Pair(address, prefixLength); } + /** + * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64". + * @hide + * + * @deprecated This method is used only for IpPrefix and LinkAddress. Since Android S, use + * {@link #parseIpAndMask(String)}, if possible. + */ + @Deprecated + public static Pair legacyParseIpAndMask(String ipAndMaskString) { + InetAddress address = null; + int prefixLength = -1; + try { + String[] pieces = ipAndMaskString.split("/", 2); + prefixLength = Integer.parseInt(pieces[1]); + if (pieces[0] == null || pieces[0].isEmpty()) { + final byte[] bytes = new byte[16]; + bytes[15] = 1; + return new Pair(Inet6Address.getByAddress( + "ip6-localhost"/* host */, bytes, 0 /* scope_id */), prefixLength); + } + + if (pieces[0].startsWith("[") + && pieces[0].endsWith("]") + && pieces[0].indexOf(':') != -1) { + pieces[0] = pieces[0].substring(1, pieces[0].length() - 1); + } + address = InetAddresses.parseNumericAddress(pieces[0]); + } catch (NullPointerException e) { // Null string. + } catch (ArrayIndexOutOfBoundsException e) { // No prefix length. + } catch (NumberFormatException e) { // Non-numeric prefix. + } catch (IllegalArgumentException e) { // Invalid IP address. + } catch (UnknownHostException e) { // IP address length is illegal + } + + if (address == null || prefixLength == -1) { + throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString); + } + + return new Pair(address, prefixLength); + } + /** * Convert a 32 char hex string into a Inet6Address. * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be From 3a87589ff6e9b2256911049b167b6073a6bed7d2 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Sun, 14 Mar 2021 15:41:15 +0900 Subject: [PATCH 091/232] Remove MessageUtils usage in VpnTransportInfo MessageUtils is a hidden utility, and including a jarjared copy in framework-connectivity would add complexity. It is only used in VpnTransportInfo, where it would parse VPN constants when the class is loaded in each process. Considering the performance and maintenance cost using numerical type codes in toString() seems to be a better tradeoff. Bug: 177046265 Test: m Change-Id: Ie71cc816f86e020b44ed1c86349b5c9204dee3cf --- framework/src/android/net/VpnTransportInfo.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java index c510079489..cd8f4c06de 100644 --- a/framework/src/android/net/VpnTransportInfo.java +++ b/framework/src/android/net/VpnTransportInfo.java @@ -22,9 +22,6 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import android.util.SparseArray; - -import com.android.internal.util.MessageUtils; import java.util.Objects; @@ -38,9 +35,6 @@ import java.util.Objects; */ @SystemApi(client = MODULE_LIBRARIES) public final class VpnTransportInfo implements TransportInfo, Parcelable { - private static final SparseArray sTypeToString = - MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"}); - /** Type of this VPN. */ public final int type; @@ -63,8 +57,7 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { @Override public String toString() { - final String typeString = sTypeToString.get(type, "VPN_TYPE_???"); - return String.format("VpnTransportInfo{%s}", typeString); + return String.format("VpnTransportInfo{type=%d}", type); } @Override From 25dcabf72d3c67be515bafcc7f94bc5e7911bcc7 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 15 Mar 2021 10:20:40 +0900 Subject: [PATCH 092/232] Add ParseException constructors to API ParseException constructors are used by both platform and mainline module code, so they can't be package-private. Removing dependencies on either side is not possible as the class itself is part of the public API, and supports APIs on both sides. Having the constructors part of the API makes the class usable by both sides. Fixes: 182705505 Test: CtsNetTestCases for APIs using the exception Change-Id: Ia396ab2fa3afaed3cf474c8e60f72fc7f3f4fded --- framework/api/module-lib-current.txt | 5 +++++ framework/src/android/net/ParseException.java | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 4b3336644e..b04e248ca6 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -25,6 +25,11 @@ package android.net { field public static final int TRANSPORT_TEST = 7; // 0x7 } + public class ParseException extends java.lang.RuntimeException { + ctor public ParseException(@NonNull String); + ctor public ParseException(@NonNull String, @NonNull Throwable); + } + public final class TcpRepairWindow { ctor public TcpRepairWindow(int, int, int, int, int, int); field public final int maxWindow; diff --git a/framework/src/android/net/ParseException.java b/framework/src/android/net/ParseException.java index bcfdd7ef09..ca6d012dfe 100644 --- a/framework/src/android/net/ParseException.java +++ b/framework/src/android/net/ParseException.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.NonNull; +import android.annotation.SystemApi; /** * Thrown when parsing failed. @@ -25,12 +26,16 @@ import android.annotation.NonNull; public class ParseException extends RuntimeException { public String response; - ParseException(@NonNull String response) { + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public ParseException(@NonNull String response) { super(response); this.response = response; } - ParseException(@NonNull String response, @NonNull Throwable cause) { + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public ParseException(@NonNull String response, @NonNull Throwable cause) { super(response, cause); this.response = response; } From c0d25c19b383b7523d76457b4fee729eddd604ef Mon Sep 17 00:00:00 2001 From: lifr Date: Tue, 9 Mar 2021 17:24:46 +0800 Subject: [PATCH 093/232] [CS15]Do not use hidden API of PlatformProperties ConnectivityService is going to become a mainline module, and it will not able to use hidden method anymore. Using PlatformProperties as a static library instead of hidden API. Bug: 170917042 Test: atest FrameworksNetTests Merged-In: I3a3deca5d2e0f690db8c0061de2db2217376d268 Change-Id: I3a3deca5d2e0f690db8c0061de2db2217376d268 --- service/Android.bp | 3 ++- service/jarjar-rules.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/service/Android.bp b/service/Android.bp index 2fb9f72fea..856f3b8533 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -66,6 +66,7 @@ java_library { "net-utils-device-common", "net-utils-framework-common", "netd-client", + "PlatformProperties", ], apex_available: [ "//apex_available:platform", @@ -76,7 +77,7 @@ java_library { java_library { name: "service-connectivity", installable: true, - static_libs:[ + static_libs: [ "service-connectivity-pre-jarjar", ], jarjar_rules: "jarjar-rules.txt", diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt index d8c60a428e..a7b419b020 100644 --- a/service/jarjar-rules.txt +++ b/service/jarjar-rules.txt @@ -1,3 +1,4 @@ +rule android.sysprop.** com.android.connectivity.sysprop.@1 rule com.android.net.module.util.** com.android.connectivity.net-utils.@1 rule com.android.modules.utils.** com.android.connectivity.modules-utils.@1 From ed0fe3ecd38d25a86b1e68fd52d0a60dfab2e907 Mon Sep 17 00:00:00 2001 From: Aaron Huang Date: Mon, 18 Jan 2021 15:28:01 +0800 Subject: [PATCH 094/232] Make PacProxyService be a system service PacProxyInstaller class is running a thread all the time and is listening to intent ACTION_PAC_REFRESH so it would be better to make it be a system service with a manager class PacProxyManager which is obtained with getSystemService(PacProxyManager.class). Besides, rename PacProxyInstaller to PacProxyService will be easier to know it's the service for PacProxyManager. ConnectivityService is going to be a mainline module and it needs constructor of PacProxyService to be SystemApi. However, in current design, it needs to pass a handler and an int arguments to the constructor which would be difficult to maintain if just expose the constructor directly. So, define a listener for the event that the current PAC proxy has been installed so that the handler and the int arguments can be removed from the constructor. Bug: 177035719 Test: FrameworksNetTests Change-Id: I2abff75ec59a17628ef006aad348c53fadbed076 --- framework/src/android/net/ProxyInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/android/net/ProxyInfo.java b/framework/src/android/net/ProxyInfo.java index 229db0d717..745e20f154 100644 --- a/framework/src/android/net/ProxyInfo.java +++ b/framework/src/android/net/ProxyInfo.java @@ -129,7 +129,7 @@ public class ProxyInfo implements Parcelable { } /** - * Only used in PacProxyInstaller after Local Proxy is bound. + * Only used in PacProxyService after Local Proxy is bound. * @hide */ public ProxyInfo(@NonNull Uri pacFileUrl, int localProxyPort) { From 3f0e7dd4e78aefb59361ceeb65f447f6ddd7a8e9 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 15 Mar 2021 07:27:44 +0000 Subject: [PATCH 095/232] Revert "Remove connectivity dependency on Preconditions" This reverts commit fa5eacc89f19bf96ed7de600a1a81b90454132f7. Reason for revert: Build broken: b/182721112 Change-Id: Ibc84ec6d7900fdcf0bc14cd7036f9c08287711db --- .../net/ConnectivityDiagnosticsManager.java | 5 ++- .../src/android/net/ConnectivityManager.java | 44 ++++++++----------- framework/src/android/net/MacAddress.java | 10 ++--- .../src/android/net/NetworkCapabilities.java | 11 +++-- .../android/net/StaticIpConfiguration.java | 3 +- .../src/android/net/TestNetworkManager.java | 7 +-- 6 files changed, 38 insertions(+), 42 deletions(-) diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.java b/framework/src/android/net/ConnectivityDiagnosticsManager.java index 3598ebc701..5234494973 100644 --- a/framework/src/android/net/ConnectivityDiagnosticsManager.java +++ b/framework/src/android/net/ConnectivityDiagnosticsManager.java @@ -28,6 +28,7 @@ import android.os.PersistableBundle; import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -69,8 +70,8 @@ public class ConnectivityDiagnosticsManager { /** @hide */ public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) { - mContext = Objects.requireNonNull(context, "missing context"); - mService = Objects.requireNonNull(service, "missing IConnectivityManager"); + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); } /** @hide */ diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index e463a7cd31..45ed3179d7 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -69,6 +69,7 @@ import android.util.SparseIntArray; import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; import com.android.internal.util.Protocol; import libcore.net.event.NetworkEventDispatcher; @@ -1732,9 +1733,7 @@ public class ConnectivityManager { // Map from type to transports. final int NOT_FOUND = -1; final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND); - if (transport == NOT_FOUND) { - throw new IllegalArgumentException("unknown legacy type: " + type); - } + Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type); nc.addTransportType(transport); // Map from type to capabilities. @@ -1839,8 +1838,8 @@ public class ConnectivityManager { } private PacketKeepalive(Network network, PacketKeepaliveCallback callback) { - Objects.requireNonNull(network, "network cannot be null"); - Objects.requireNonNull(callback, "callback cannot be null"); + Preconditions.checkNotNull(network, "network cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); mNetwork = network; mExecutor = Executors.newSingleThreadExecutor(); mCallback = new ISocketKeepaliveCallback.Stub() { @@ -2215,9 +2214,7 @@ public class ConnectivityManager { */ public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) { INetworkActivityListener rl = mNetworkActivityListeners.get(l); - if (rl == null) { - throw new IllegalArgumentException("Listener was not registered."); - } + Preconditions.checkArgument(rl != null, "Listener was not registered."); try { mService.registerNetworkActivityListener(rl); } catch (RemoteException e) { @@ -2245,8 +2242,8 @@ public class ConnectivityManager { * {@hide} */ public ConnectivityManager(Context context, IConnectivityManager service) { - mContext = Objects.requireNonNull(context, "missing context"); - mService = Objects.requireNonNull(service, "missing IConnectivityManager"); + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE); sInstance = this; } @@ -2513,7 +2510,7 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int type, boolean showProvisioningUi, final OnStartTetheringCallback callback, Handler handler) { - Objects.requireNonNull(callback, "OnStartTetheringCallback cannot be null."); + Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null."); final Executor executor = new Executor() { @Override @@ -2606,7 +2603,7 @@ public class ConnectivityManager { public void registerTetheringEventCallback( @NonNull @CallbackExecutor Executor executor, @NonNull final OnTetheringEventCallback callback) { - Objects.requireNonNull(callback, "OnTetheringEventCallback cannot be null."); + Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null."); final TetheringEventCallback tetherCallback = new TetheringEventCallback() { @@ -2904,7 +2901,7 @@ public class ConnectivityManager { public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi, @NonNull @CallbackExecutor Executor executor, @NonNull final OnTetheringEntitlementResultListener listener) { - Objects.requireNonNull(listener, "TetheringEntitlementResultListener cannot be null."); + Preconditions.checkNotNull(listener, "TetheringEntitlementResultListener cannot be null."); ResultReceiver wrappedListener = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -3528,7 +3525,7 @@ public class ConnectivityManager { } CallbackHandler(Handler handler) { - this(Objects.requireNonNull(handler, "Handler cannot be null.").getLooper()); + this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper()); } @Override @@ -3626,9 +3623,9 @@ public class ConnectivityManager { int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { printStackTrace(); checkCallbackNotNull(callback); - if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) { - throw new IllegalArgumentException("null NetworkCapabilities"); - } + Preconditions.checkArgument( + reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null, + "null NetworkCapabilities"); final NetworkRequest request; final String callingPackageName = mContext.getOpPackageName(); try { @@ -3974,17 +3971,15 @@ public class ConnectivityManager { } private static void checkPendingIntentNotNull(PendingIntent intent) { - Objects.requireNonNull(intent, "PendingIntent cannot be null."); + Preconditions.checkNotNull(intent, "PendingIntent cannot be null."); } private static void checkCallbackNotNull(NetworkCallback callback) { - Objects.requireNonNull(callback, "null NetworkCallback"); + Preconditions.checkNotNull(callback, "null NetworkCallback"); } private static void checkTimeout(int timeoutMs) { - if (timeoutMs <= 0) { - throw new IllegalArgumentException("timeoutMs must be strictly positive."); - } + Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive."); } /** @@ -4234,9 +4229,8 @@ public class ConnectivityManager { // Find all requests associated to this callback and stop callback triggers immediately. // Callback is reusable immediately. http://b/20701525, http://b/35921499. synchronized (sCallbacks) { - if (networkCallback.networkRequest == null) { - throw new IllegalArgumentException("NetworkCallback was not registered"); - } + Preconditions.checkArgument(networkCallback.networkRequest != null, + "NetworkCallback was not registered"); if (networkCallback.networkRequest == ALREADY_UNREGISTERED) { Log.d(TAG, "NetworkCallback was already unregistered"); return; diff --git a/framework/src/android/net/MacAddress.java b/framework/src/android/net/MacAddress.java index 26a504a29c..c83c23a4b6 100644 --- a/framework/src/android/net/MacAddress.java +++ b/framework/src/android/net/MacAddress.java @@ -25,6 +25,7 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; import com.android.net.module.util.MacAddressUtils; import java.lang.annotation.Retention; @@ -33,7 +34,6 @@ import java.net.Inet6Address; import java.net.UnknownHostException; import java.security.SecureRandom; import java.util.Arrays; -import java.util.Objects; /** * Representation of a MAC address. @@ -229,7 +229,7 @@ public final class MacAddress implements Parcelable { * @hide */ public static @NonNull byte[] byteAddrFromStringAddr(String addr) { - Objects.requireNonNull(addr); + Preconditions.checkNotNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { throw new IllegalArgumentException(addr + " was not a valid MAC address"); @@ -275,7 +275,7 @@ public final class MacAddress implements Parcelable { // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr)) // that avoids the allocation of an intermediary byte[]. private static long longAddrFromStringAddr(String addr) { - Objects.requireNonNull(addr); + Preconditions.checkNotNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { throw new IllegalArgumentException(addr + " was not a valid MAC address"); @@ -364,8 +364,8 @@ public final class MacAddress implements Parcelable { * */ public boolean matches(@NonNull MacAddress baseAddress, @NonNull MacAddress mask) { - Objects.requireNonNull(baseAddress); - Objects.requireNonNull(mask); + Preconditions.checkNotNull(baseAddress); + Preconditions.checkNotNull(mask); return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr); } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 7a693444e6..c82cd3b4f3 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -34,6 +34,7 @@ import android.util.ArraySet; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkCapabilitiesUtils; @@ -2072,9 +2073,8 @@ public final class NetworkCapabilities implements Parcelable { } private static void checkValidTransportType(@Transport int transport) { - if (!isValidTransport(transport)) { - throw new IllegalArgumentException("Invalid TransportType " + transport); - } + Preconditions.checkArgument( + isValidTransport(transport), "Invalid TransportType " + transport); } private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) { @@ -2082,9 +2082,8 @@ public final class NetworkCapabilities implements Parcelable { } private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) { - if (!isValidCapability(capability)) { - throw new IllegalArgumentException("NetworkCapability " + capability + "out of range"); - } + Preconditions.checkArgument(isValidCapability(capability), + "NetworkCapability " + capability + "out of range"); } /** diff --git a/framework/src/android/net/StaticIpConfiguration.java b/framework/src/android/net/StaticIpConfiguration.java index 7904f7a4ec..ce545974f5 100644 --- a/framework/src/android/net/StaticIpConfiguration.java +++ b/framework/src/android/net/StaticIpConfiguration.java @@ -24,6 +24,7 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; import com.android.net.module.util.InetAddressUtils; import java.net.InetAddress; @@ -152,7 +153,7 @@ public final class StaticIpConfiguration implements Parcelable { * @return The {@link Builder} for chaining. */ public @NonNull Builder setDnsServers(@NonNull Iterable dnsServers) { - Objects.requireNonNull(dnsServers); + Preconditions.checkNotNull(dnsServers); mDnsServers = dnsServers; return this; } diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java index a7a62351e5..a174a7be85 100644 --- a/framework/src/android/net/TestNetworkManager.java +++ b/framework/src/android/net/TestNetworkManager.java @@ -21,9 +21,10 @@ import android.annotation.SystemApi; import android.os.IBinder; import android.os.RemoteException; +import com.android.internal.util.Preconditions; + import java.util.Arrays; import java.util.Collection; -import java.util.Objects; /** * Class that allows creation and management of per-app, test-only networks @@ -49,7 +50,7 @@ public class TestNetworkManager { /** @hide */ public TestNetworkManager(@NonNull ITestNetworkManager service) { - mService = Objects.requireNonNull(service, "missing ITestNetworkManager"); + mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager"); } /** @@ -92,7 +93,7 @@ public class TestNetworkManager { */ public void setupTestNetwork( @NonNull LinkProperties lp, boolean isMetered, @NonNull IBinder binder) { - Objects.requireNonNull(lp, "Invalid LinkProperties"); + Preconditions.checkNotNull(lp, "Invalid LinkProperties"); setupTestNetwork(lp.getInterfaceName(), lp, isMetered, new int[0], binder); } From d637272b9cdcc5e363424e4fd6ace587a994c796 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Mon, 21 Dec 2020 18:36:52 +0900 Subject: [PATCH 096/232] [NS01] Add NetworkScore As attested by numerous TODOs in the code, a new way of representing network quality and policy is needed instead of an int. An int representing the quality of the network requires all parties using it to know how all other parties are using it, and implementation details about the decision algorithm. For all intents and purposes, the selection is left to individual network factories who try to achieve a desired result while piecing together all possible states of the system. As the number of such cases and desires increases, this becomes both intractable and unmaintainable. Indeed, at this time in the codebase nobody can really predict exactly how a given change in score will affect selection across the board, and it is essentially impossible to figure out the behavior of network selection by inspecting the code because the moving parts are scattered throughout the entire codebase. Having an object encapsulating policy and quality values will let us centralize the selection and make it again possible to maintain without knowledge of all behaviors of all network factories. It will also provide better guarantees of respecting policy, and allow bugfixes that were not possible before because they'd touch too many parts of the code. Test: FrameworksNetTests FrameworksWifiTests NetworkStackTests Change-Id: I3185a6412b9b659798faf0c6882699e9c63cc115 --- .../src/android/net/ConnectivityManager.java | 20 +-------- .../src/android/net/IConnectivityManager.aidl | 3 +- framework/src/android/net/NetworkAgent.java | 42 ++++++++++++++----- .../aidl/INetworkAgentRegistry.aidl | 5 ++- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 8c66db9a20..aec6b0a6ca 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3205,10 +3205,6 @@ public class ConnectivityManager { } } - // TODO : remove this method. It is a stopgap measure to help sheperding a number - // of dependent changes that would conflict throughout the automerger graph. Having this - // temporarily helps with the process of going through with all these dependent changes across - // the entire tree. /** * @hide * Register a NetworkAgent with ConnectivityService. @@ -3218,20 +3214,8 @@ public class ConnectivityManager { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, int score, NetworkAgentConfig config) { - return registerNetworkAgent(na, ni, lp, nc, score, config, NetworkProvider.ID_NONE); - } - - /** - * @hide - * Register a NetworkAgent with ConnectivityService. - * @return Network corresponding to NetworkAgent. - */ - @RequiresPermission(anyOf = { - NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_FACTORY}) - public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) { + NetworkCapabilities nc, @NonNull NetworkScore score, NetworkAgentConfig config, + int providerId) { try { return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId); } catch (RemoteException e) { diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index f9393e315b..1bbf1a95fc 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -30,6 +30,7 @@ import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; +import android.net.NetworkScore; import android.net.NetworkState; import android.net.NetworkStateSnapshot; import android.net.OemNetworkPreferences; @@ -138,7 +139,7 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, - in NetworkCapabilities nc, int score, in NetworkAgentConfig config, + in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config, in int factorySerialNumber); NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index 27aa15d1e1..b3ab0ee8bd 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -371,6 +371,14 @@ public abstract class NetworkAgent { return ni; } + // Temporary backward compatibility constructor + public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, + @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { + this(context, looper, logTag, nc, lp, + new NetworkScore.Builder().setLegacyInt(score).build(), config, provider); + } + /** * Create a new network agent. * @param context a {@link Context} to get system services from. @@ -382,10 +390,12 @@ public abstract class NetworkAgent { * @param score the initial score of this network. Update with sendNetworkScore. * @param config an immutable {@link NetworkAgentConfig} for this agent. * @param provider the {@link NetworkProvider} managing this agent. + * @hide TODO : unhide when impl is complete */ public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, - @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, - @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, + @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, + @Nullable NetworkProvider provider) { this(looper, context, logTag, nc, lp, score, config, provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(), getLegacyNetworkInfo(config)); @@ -395,12 +405,12 @@ public abstract class NetworkAgent { public final Context context; public final NetworkCapabilities capabilities; public final LinkProperties properties; - public final int score; + public final NetworkScore score; public final NetworkAgentConfig config; public final NetworkInfo info; InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities, - @NonNull LinkProperties properties, int score, @NonNull NetworkAgentConfig config, - @NonNull NetworkInfo info) { + @NonNull LinkProperties properties, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, @NonNull NetworkInfo info) { this.context = context; this.capabilities = capabilities; this.properties = properties; @@ -412,8 +422,9 @@ public abstract class NetworkAgent { private volatile InitialConfiguration mInitialConfiguration; private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag, - @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, int score, - @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) { + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, + @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId, + @NonNull NetworkInfo ni) { mHandler = new NetworkAgentHandler(looper); LOG_TAG = logTag; mNetworkInfo = new NetworkInfo(ni); @@ -872,16 +883,25 @@ public abstract class NetworkAgent { queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc)); } + /** + * Must be called by the agent to update the score of this network. + * + * @param score the new score. + * @hide TODO : unhide when impl is complete + */ + public final void sendNetworkScore(@NonNull NetworkScore score) { + Objects.requireNonNull(score); + queueOrSendMessage(reg -> reg.sendScore(score)); + } + /** * Must be called by the agent to update the score of this network. * * @param score the new score, between 0 and 99. + * deprecated use sendNetworkScore(NetworkScore) TODO : remove in S. */ public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) { - if (score < 0) { - throw new IllegalArgumentException("Score must be >= 0"); - } - queueOrSendMessage(reg -> reg.sendScore(score)); + sendNetworkScore(new NetworkScore.Builder().setLegacyInt(score).build()); } /** diff --git a/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl index f0193db5c2..18d26a7e4b 100644 --- a/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl +++ b/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl @@ -19,11 +19,12 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.NetworkScore; import android.net.QosSession; import android.telephony.data.EpsBearerQosSessionAttributes; /** - * Interface for NetworkAgents to send network network properties. + * Interface for NetworkAgents to send network properties. * @hide */ oneway interface INetworkAgentRegistry { @@ -31,7 +32,7 @@ oneway interface INetworkAgentRegistry { void sendLinkProperties(in LinkProperties lp); // TODO: consider replacing this by "markConnected()" and removing void sendNetworkInfo(in NetworkInfo info); - void sendScore(int score); + void sendScore(in NetworkScore score); void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial); void sendSocketKeepaliveEvent(int slot, int reason); void sendUnderlyingNetworks(in @nullable List networks); From c296fccca186165e3d9b14123d2b34a96a1dffd2 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Mon, 15 Mar 2021 17:24:12 +0800 Subject: [PATCH 097/232] Add comments to describe the value of converting hex to decimal Bug: 172183305 Test: N/A Change-Id: Id274295d6c8c97d3014214f875168ff968f79bb6 --- framework/src/android/net/ConnectivityManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index e9107b67e4..0d4489c7e8 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5114,9 +5114,9 @@ public class ConnectivityManager { } // The first network ID of IPSec tunnel interface. - private static final int TUN_INTF_NETID_START = 0xFC00; + private static final int TUN_INTF_NETID_START = 0xFC00; // 0xFC00 = 64512 // The network ID range of IPSec tunnel interface. - private static final int TUN_INTF_NETID_RANGE = 0x0400; + private static final int TUN_INTF_NETID_RANGE = 0x0400; // 0x0400 = 1024 /** * Get the network ID range reserved for IPSec tunnel interfaces. From ffc407733390c798587e87b03666c3248a88107e Mon Sep 17 00:00:00 2001 From: paulhu Date: Tue, 9 Mar 2021 16:12:48 +0800 Subject: [PATCH 098/232] Replace interal okhttp APIs Connectivity is becoming a mainline module in S but mainline modules are not allowed to use non-formal APIs. Thus, replace internal okhttp APIs to stable libcore APIs which are created for using HttpURLConnectionFactory. Bug: 182238821 Test: atest FrameworksNetTests Change-Id: I56ba1b9e6e94f9c6519c3f1c8f0c5993fccbe185 --- framework/src/android/net/Network.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java index 46141e0d0c..7245db3b17 100644 --- a/framework/src/android/net/Network.java +++ b/framework/src/android/net/Network.java @@ -30,10 +30,10 @@ import android.system.OsConstants; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; -import com.android.okhttp.internalandroidapi.Dns; -import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory; import libcore.io.IoUtils; +import libcore.net.http.Dns; +import libcore.net.http.HttpURLConnectionFactory; import java.io.FileDescriptor; import java.io.IOException; @@ -299,7 +299,7 @@ public class Network implements Parcelable { // Set configuration on the HttpURLConnectionFactory that will be good for all // connections created by this Network. Configuration that might vary is left // until openConnection() and passed as arguments. - HttpURLConnectionFactory urlConnectionFactory = new HttpURLConnectionFactory(); + HttpURLConnectionFactory urlConnectionFactory = HttpURLConnectionFactory.createInstance(); urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup // A private connection pool just for this Network. urlConnectionFactory.setNewConnectionPool(httpMaxConnections, From 5f161d22cd878b817cc7b7cb65216f7bc4e7ff8b Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 9 Mar 2021 13:35:25 +0900 Subject: [PATCH 099/232] Remove usage of networkAttributes networkAttributes is a legacy configuration that is now only used to configure which legacy type networks are supported, and what the restore timer is for that network type, for the deprecated startUsingNetworkFeature API. Use a dedicated resource for the restore timers, and build supported legacy network types using hasSystemFeature for wifi, wifi p2p, bluetooth, proxy types, and TelephonyManager.isDataCapable for the mobile types. Bug: 146206136 Test: atest FrameworksNetTests Change-Id: I3a771d3de6c5e912f18d2834e3a50af797ac4991 --- .../ServiceConnectivityResources/res/values/config.xml | 10 ++++++++++ .../res/values/overlayable.xml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml index 7d98c76a40..06c81921fd 100644 --- a/service/ServiceConnectivityResources/res/values/config.xml +++ b/service/ServiceConnectivityResources/res/values/config.xml @@ -42,4 +42,14 @@ --> + + 2,60000 + 3,60000 + 4,60000 + 5,60000 + 10,60000 + 11,60000 + 12,60000 + + \ No newline at end of file diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml index 00ec2df0e6..da8aee5627 100644 --- a/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -17,11 +17,11 @@ + - From e2cd02d9ab9950acb82090cd4450ffc7ae853722 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Tue, 16 Mar 2021 17:11:14 +0800 Subject: [PATCH 100/232] Change the parameter type from ContentResolver to Context Context is more useful than ContentResolver, it can provide more information if we want to change the behavior in the future. Bug: 172183305 Test: atest FrameworksNetTests Change-Id: I5702c7d74b862a76558b94f1abe2c6df9eb7f097 Merged-In: I5702c7d74b862a76558b94f1abe2c6df9eb7f097 --- framework/api/module-lib-current.txt | 2 +- framework/src/android/net/ConnectivityManager.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index c3b1800af2..cc95a7cc24 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -8,7 +8,7 @@ package android.net { public class ConnectivityManager { method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshot(); method @NonNull public static android.util.Range getIpSecNetIdRange(); - method @NonNull public static String getPrivateDnsMode(@NonNull android.content.ContentResolver); + method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 5907f23799..417246318a 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5124,7 +5124,8 @@ public class ConnectivityManager { /** * Get private DNS mode from settings. * - * @param cr The ContentResolver to query private DNS mode from settings. + * @param context The Context to get its ContentResolver to query the private DNS mode from + * settings. * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants. * * @hide @@ -5132,7 +5133,8 @@ public class ConnectivityManager { @SystemApi(client = MODULE_LIBRARIES) @NonNull @PrivateDnsMode - public static String getPrivateDnsMode(@NonNull ContentResolver cr) { + public static String getPrivateDnsMode(@NonNull Context context) { + final ContentResolver cr = context.getContentResolver(); String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE); if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE); // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose From ad565e26d9e61d21674affb21c307b84a3096eb8 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Thu, 25 Feb 2021 17:23:40 +0900 Subject: [PATCH 101/232] Public API for per-profile network preference. This patch defines the API, but does not make it public yet as there is no implementation yet. Test: none so far Change-Id: I854a952dfe35cc80847eb62f522b1667b8e9b8a0 --- .../src/android/net/ConnectivityManager.java | 71 +++++++++++++++++++ .../src/android/net/IConnectivityManager.aidl | 5 ++ 2 files changed, 76 insertions(+) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 8b669284ad..dab9491095 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -64,6 +64,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.os.UserHandle; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -970,6 +971,33 @@ public class ConnectivityManager { } } + /** + * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}. + * Specify that the traffic for this user should by follow the default rules. + * @hide + */ + // TODO : @SystemApi + public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; + + /** + * Preference for {@link #setNetworkPreferenceForUser(UserHandle, int, Executor, Runnable)}. + * Specify that the traffic for this user should by default go on a network with + * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network + * if no such network is available. + * @hide + */ + // TODO : @SystemApi + public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + PROFILE_NETWORK_PREFERENCE_DEFAULT, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE + }) + public @interface ProfileNetworkPreference { + } + /** * Specifies the preferred network type. When the device has more * than one type available the preferred network type will be used. @@ -5053,6 +5081,8 @@ public class ConnectivityManager { * OnSetOemNetworkPreferenceListener)}. * @hide */ + // TODO : remove this in favor of a vanilla runnable to follow API guidance to use + // functional interfaces when they are appropriate. @SystemApi public interface OnSetOemNetworkPreferenceListener { /** @@ -5104,6 +5134,47 @@ public class ConnectivityManager { } } + /** + * Request that a user profile is put by default on a network matching a given preference. + * + * See the documentation for the individual preferences for a description of the supported + * behaviors. + * + * @param profile the profile concerned. + * @param preference the preference for this profile. + * @param executor an executor to execute the listener on. Optional if listener is null. + * @param listener an optional listener to listen for completion of the operation. + * @throws IllegalArgumentException if {@code profile} is not a valid user profile. + * @throws SecurityException if missing the appropriate permissions. + * @hide + */ + // TODO : @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_STACK) + public void setProfileNetworkPreference(@NonNull final UserHandle profile, + @ProfileNetworkPreference final int preference, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final Runnable listener) { + if (null != listener) { + Objects.requireNonNull(executor, "Pass a non-null executor, or a null listener"); + } + final IOnCompleteListener proxy; + if (null == listener) { + proxy = null; + } else { + proxy = new IOnCompleteListener.Stub() { + @Override + public void onComplete() { + executor.execute(listener::run); + } + }; + } + try { + mService.setProfileNetworkPreference(profile, preference, proxy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + // The first network ID of IPSec tunnel interface. private static final int TUN_INTF_NETID_START = 0xFC00; // 0xFC00 = 64512 // The network ID range of IPSec tunnel interface. diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 1bbf1a95fc..d1a23ecf54 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; +import android.net.IOnCompleteListener; import android.net.IOnSetOemNetworkPreferenceListener; import android.net.INetworkActivityListener; import android.net.IQosCallback; @@ -43,6 +44,7 @@ import android.os.Messenger; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.ResultReceiver; +import android.os.UserHandle; import com.android.connectivity.aidl.INetworkAgent; @@ -216,4 +218,7 @@ interface IConnectivityManager void setOemNetworkPreference(in OemNetworkPreferences preference, in IOnSetOemNetworkPreferenceListener listener); + + void setProfileNetworkPreference(in UserHandle profile, int preference, + in IOnCompleteListener listener); } From 76b0c7efda67ffa40a934034908355d9dcc14320 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Thu, 25 Feb 2021 21:46:34 +0900 Subject: [PATCH 102/232] Implement setNetworkPreferenceForUser. Test: FrameworksNetTests Change-Id: I8f18083b5857289892fe8adea5f5ea3f5dbe0809 --- framework/src/android/net/OemNetworkPreferences.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/src/android/net/OemNetworkPreferences.java b/framework/src/android/net/OemNetworkPreferences.java index 48bd29769f..5a76cd6d6b 100644 --- a/framework/src/android/net/OemNetworkPreferences.java +++ b/framework/src/android/net/OemNetworkPreferences.java @@ -72,6 +72,14 @@ public final class OemNetworkPreferences implements Parcelable { @NonNull private final Bundle mNetworkMappings; + /** + * Return whether this object is empty. + * @hide + */ + public boolean isEmpty() { + return mNetworkMappings.keySet().size() == 0; + } + /** * Return the currently built application package name to {@link OemNetworkPreference} mappings. * @return the current network preferences map. From 0a4aefcec27f6a56524bc9ac9227f579619b5863 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Wed, 3 Mar 2021 16:37:13 +0900 Subject: [PATCH 103/232] Expose the enterprise per-profile networking API. Also unify the listener for Oem settings, which have never been released as public API (it is slated to be released in S). Test: FrameworksNetTests Change-Id: I84ba363dd0ec03871c37b1c3a31e5557d9aa12e7 --- framework/api/module-lib-current.txt | 1 + framework/api/system-current.txt | 8 ++--- .../src/android/net/ConnectivityManager.java | 34 +++++++------------ .../src/android/net/IConnectivityManager.aidl | 3 +- .../IOnSetOemNetworkPreferenceListener.aidl | 23 ------------- 5 files changed, 17 insertions(+), 52 deletions(-) delete mode 100644 framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index c3b1800af2..088bb41330 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -11,6 +11,7 @@ package android.net { method @NonNull public static String getPrivateDnsMode(@NonNull android.content.ContentResolver); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); field public static final String PRIVATE_DNS_MODE_OFF = "off"; field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index a98f14ea94..4dca411cca 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -56,7 +56,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); - method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); + method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); @@ -67,6 +67,8 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; + field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0 + field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 @@ -78,10 +80,6 @@ package android.net { field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd } - public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { - method public void onComplete(); - } - @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); method @Deprecated public void onTetheringFailed(); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index dab9491095..ff07ee4ebd 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -976,7 +976,7 @@ public class ConnectivityManager { * Specify that the traffic for this user should by follow the default rules. * @hide */ - // TODO : @SystemApi + @SystemApi public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; /** @@ -986,7 +986,7 @@ public class ConnectivityManager { * if no such network is available. * @hide */ - // TODO : @SystemApi + @SystemApi public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; /** @hide */ @@ -5076,21 +5076,6 @@ public class ConnectivityManager { TYPE_NONE, new CallbackHandler(handler)); } - /** - * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor, - * OnSetOemNetworkPreferenceListener)}. - * @hide - */ - // TODO : remove this in favor of a vanilla runnable to follow API guidance to use - // functional interfaces when they are appropriate. - @SystemApi - public interface OnSetOemNetworkPreferenceListener { - /** - * Called when setOemNetworkPreference() successfully completes. - */ - void onComplete(); - } - /** * Used by automotive devices to set the network preferences used to direct traffic at an * application level as per the given OemNetworkPreferences. An example use-case would be an @@ -5113,16 +5098,16 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, @Nullable @CallbackExecutor final Executor executor, - @Nullable final OnSetOemNetworkPreferenceListener listener) { + @Nullable final Runnable listener) { Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); if (null != listener) { Objects.requireNonNull(executor, "Executor must be non-null"); } - final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null : - new IOnSetOemNetworkPreferenceListener.Stub() { + final IOnCompleteListener listenerInternal = listener == null ? null : + new IOnCompleteListener.Stub() { @Override public void onComplete() { - executor.execute(listener::onComplete); + executor.execute(listener::run); } }; @@ -5148,7 +5133,12 @@ public class ConnectivityManager { * @throws SecurityException if missing the appropriate permissions. * @hide */ - // TODO : @SystemApi + // This function is for establishing per-profile default networking and can only be called by + // the device policy manager, running as the system server. It would make no sense to call it + // on a context for a user because it does not establish a setting on behalf of a user, rather + // it establishes a setting for a user on behalf of the DPM. + @SuppressLint({"UserHandle"}) + @SystemApi(client = MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull final UserHandle profile, @ProfileNetworkPreference final int preference, diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index d1a23ecf54..d83cc163b5 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -21,7 +21,6 @@ import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; import android.net.IOnCompleteListener; -import android.net.IOnSetOemNetworkPreferenceListener; import android.net.INetworkActivityListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; @@ -217,7 +216,7 @@ interface IConnectivityManager void unregisterQosCallback(in IQosCallback callback); void setOemNetworkPreference(in OemNetworkPreferences preference, - in IOnSetOemNetworkPreferenceListener listener); + in IOnCompleteListener listener); void setProfileNetworkPreference(in UserHandle profile, int preference, in IOnCompleteListener listener); diff --git a/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl b/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl deleted file mode 100644 index 7979afc54f..0000000000 --- a/framework/src/android/net/IOnSetOemNetworkPreferenceListener.aidl +++ /dev/null @@ -1,23 +0,0 @@ -/** - * - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -/** @hide */ -oneway interface IOnSetOemNetworkPreferenceListener { - void onComplete(); -} From 7664f62d37f55099e1b913373efcc7a613154dd6 Mon Sep 17 00:00:00 2001 From: junyulai Date: Fri, 12 Mar 2021 20:05:08 +0800 Subject: [PATCH 104/232] [VCN13] Implement tracking best matching network This is done by: 1. In requestNetwork, provide a basic permission check. 2. rematchNetworksAndRequests: no change, since non listen requests will be automatically processed to track best satisfying network. 3. applyNetworkReassignment: no change, since non-request will not be sent to factories. Test: atest ConnectivityServiceTest#testRegisterBestMatchingNetworkCallback_noIssueToFactory Test: atest ConnectivityServiceTest#testRegisterBestMatchingNetworkCallback_trackBestNetwork Bug: 175662146 Change-Id: I8cf4ab334df6812d84cdda160e9b72b6f54062af --- framework/src/android/net/ConnectivityManager.java | 4 ++-- framework/src/android/net/NetworkRequest.java | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 8b669284ad..81590a84a4 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -18,8 +18,8 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; +import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST; import static android.net.NetworkRequest.Type.REQUEST; -import static android.net.NetworkRequest.Type.TRACK_BEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; @@ -4249,7 +4249,7 @@ public class ConnectivityManager { @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { final NetworkCapabilities nc = request.networkCapabilities; final CallbackHandler cbHandler = new CallbackHandler(handler); - sendRequestForNetwork(nc, networkCallback, 0, TRACK_BEST, TYPE_NONE, cbHandler); + sendRequestForNetwork(nc, networkCallback, 0, LISTEN_FOR_BEST, TYPE_NONE, cbHandler); } /** diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 3fd95ee58d..dbe3ecc4d7 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -140,7 +140,7 @@ public class NetworkRequest implements Parcelable { REQUEST, BACKGROUND_REQUEST, TRACK_SYSTEM_DEFAULT, - TRACK_BEST, + LISTEN_FOR_BEST, }; /** @@ -513,6 +513,15 @@ public class NetworkRequest implements Parcelable { return type == Type.LISTEN; } + /** + * Returns true iff. this NetworkRequest is of type LISTEN_FOR_BEST. + * + * @hide + */ + public boolean isListenForBest() { + return type == Type.LISTEN_FOR_BEST; + } + /** * Returns true iff. the contained NetworkRequest is one that: * From bd1230675ec6d7171c161ed9c10b0cff4ab7162d Mon Sep 17 00:00:00 2001 From: junyulai Date: Mon, 15 Mar 2021 11:48:48 +0800 Subject: [PATCH 105/232] [VCN14] Expose registerBestMatchingNetworkCallback Test: m -j doc-comment-check-docs Bug: 175662146 Change-Id: Ie67dd2f4f8d973de37bc64a03908d7cbb7c2b7ad --- framework/api/current.txt | 1 + .../src/android/net/ConnectivityManager.java | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index 243e4ca429..f22d4b7b77 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -87,6 +87,7 @@ package android.net { method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); method public boolean isDefaultNetworkActive(); method @Deprecated public static boolean isNetworkTypeValid(int); + method public void registerBestMatchingNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 81590a84a4..d7cae2f0e5 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4241,9 +4241,27 @@ public class ConnectivityManager { } /** - * @hide + * Registers to receive notifications about the best matching network which satisfy the given + * {@link NetworkRequest}. The callbacks will continue to be called until + * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is + * called. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * {@link #registerNetworkCallback} and its variants and {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * + * @param request {@link NetworkRequest} describing this request. + * @param networkCallback The {@link NetworkCallback} that the system will call as suitable + * networks change state. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. */ - // TODO: Make it public api. @SuppressLint("ExecutorRegistration") public void registerBestMatchingNetworkCallback(@NonNull NetworkRequest request, @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { From 7cb4439a92f03b892482b814b4e903f012a97a30 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 16 Mar 2021 14:06:58 +0900 Subject: [PATCH 106/232] Move trimV4AddrZeros to libs/net The utility is @UnsupportedAppUsage, and also used by internal classes like WifiTrackerLib or Mms, so it needs to be in a shared location. Bug: 182859030 Test: m Change-Id: I25cb374f4743a5869e9da5b01d3a543a9a165c0e --- framework/src/android/net/NetworkUtils.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java index c0f262815b..c4bebc0a98 100644 --- a/framework/src/android/net/NetworkUtils.java +++ b/framework/src/android/net/NetworkUtils.java @@ -323,22 +323,7 @@ public class NetworkUtils { */ @UnsupportedAppUsage public static String trimV4AddrZeros(String addr) { - if (addr == null) return null; - String[] octets = addr.split("\\."); - if (octets.length != 4) return addr; - StringBuilder builder = new StringBuilder(16); - String result = null; - for (int i = 0; i < 4; i++) { - try { - if (octets[i].length() > 3) return addr; - builder.append(Integer.parseInt(octets[i])); - } catch (NumberFormatException e) { - return addr; - } - if (i < 3) builder.append('.'); - } - result = builder.toString(); - return result; + return Inet4AddressUtils.trimAddressZeros(addr); } /** From 611b02178d580502359aa5ab3980c62e3c92c404 Mon Sep 17 00:00:00 2001 From: Hai Shalom Date: Tue, 16 Mar 2021 20:59:42 -0700 Subject: [PATCH 107/232] CaptivePortalData: use CharSequence in VenueFriendlyName API Following up on feedback from API council, change the String type to CharSequence in the get and set Venue friendly name API. Bug: 179163405 Test: atest ConnectivityServiceTest CtsNetTestCasesLatestSdk:CaptivePortalDataTest Test: atest NetworkNotificationManagerTest NetworkMonitorTest Change-Id: Ia63974cd2ff5975bde410eb93731d4b1def36d19 --- framework/api/system-current.txt | 4 ++-- framework/src/android/net/CaptivePortalData.java | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index a98f14ea94..54b3f56301 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -18,7 +18,7 @@ package android.net { method public long getRefreshTimeMillis(); method @Nullable public android.net.Uri getUserPortalUrl(); method public int getUserPortalUrlSource(); - method @Nullable public String getVenueFriendlyName(); + method @Nullable public CharSequence getVenueFriendlyName(); method @Nullable public android.net.Uri getVenueInfoUrl(); method public int getVenueInfoUrlSource(); method public boolean isCaptive(); @@ -40,7 +40,7 @@ package android.net { method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int); - method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String); + method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable CharSequence); method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int); } diff --git a/framework/src/android/net/CaptivePortalData.java b/framework/src/android/net/CaptivePortalData.java index eafda4d2d6..82dbd0fb1f 100644 --- a/framework/src/android/net/CaptivePortalData.java +++ b/framework/src/android/net/CaptivePortalData.java @@ -42,7 +42,7 @@ public final class CaptivePortalData implements Parcelable { private final long mByteLimit; private final long mExpiryTimeMillis; private final boolean mCaptive; - private final String mVenueFriendlyName; + private final CharSequence mVenueFriendlyName; private final int mVenueInfoUrlSource; private final int mUserPortalUrlSource; @@ -65,7 +65,7 @@ public final class CaptivePortalData implements Parcelable { private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive, - String venueFriendlyName, int venueInfoUrlSource, int userPortalUrlSource) { + CharSequence venueFriendlyName, int venueInfoUrlSource, int userPortalUrlSource) { mRefreshTimeMillis = refreshTimeMillis; mUserPortalUrl = userPortalUrl; mVenueInfoUrl = venueInfoUrl; @@ -80,7 +80,7 @@ public final class CaptivePortalData implements Parcelable { private CaptivePortalData(Parcel p) { this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(), - p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(), + p.readLong(), p.readLong(), p.readBoolean(), p.readCharSequence(), p.readInt(), p.readInt()); } @@ -98,7 +98,7 @@ public final class CaptivePortalData implements Parcelable { dest.writeLong(mByteLimit); dest.writeLong(mExpiryTimeMillis); dest.writeBoolean(mCaptive); - dest.writeString(mVenueFriendlyName); + dest.writeCharSequence(mVenueFriendlyName); dest.writeInt(mVenueInfoUrlSource); dest.writeInt(mUserPortalUrlSource); } @@ -114,7 +114,7 @@ public final class CaptivePortalData implements Parcelable { private long mBytesRemaining = -1; private long mExpiryTime = -1; private boolean mCaptive; - private String mVenueFriendlyName; + private CharSequence mVenueFriendlyName; private @CaptivePortalDataSource int mVenueInfoUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER; private @CaptivePortalDataSource int mUserPortalUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER; @@ -228,7 +228,7 @@ public final class CaptivePortalData implements Parcelable { * Set the venue friendly name. */ @NonNull - public Builder setVenueFriendlyName(@Nullable String venueFriendlyName) { + public Builder setVenueFriendlyName(@Nullable CharSequence venueFriendlyName) { mVenueFriendlyName = venueFriendlyName; return this; } @@ -321,7 +321,7 @@ public final class CaptivePortalData implements Parcelable { * Get the venue friendly name */ @Nullable - public String getVenueFriendlyName() { + public CharSequence getVenueFriendlyName() { return mVenueFriendlyName; } From bef6b09cd997f48832179064327f1d002010deb7 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Wed, 17 Mar 2021 14:33:24 +0900 Subject: [PATCH 108/232] Move constants to MODULE_LIB These constants are used by a MODULE_LIBRARIES API, they should have the same visibility. Test: ConnectivityServiceTest Change-Id: I14cb189d949fe552f463cae3002801fd8cf8230c --- framework/api/module-lib-current.txt | 2 ++ framework/api/system-current.txt | 2 -- framework/src/android/net/ConnectivityManager.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 088bb41330..c7fbbd85ba 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -16,6 +16,8 @@ package android.net { field public static final String PRIVATE_DNS_MODE_OFF = "off"; field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; + field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0 + field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 } public final class NetworkAgentConfig implements android.os.Parcelable { diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 4dca411cca..031bb916c4 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -67,8 +67,6 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; - field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0 - field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 59d213135e..3f1ef61570 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -976,7 +976,7 @@ public class ConnectivityManager { * Specify that the traffic for this user should by follow the default rules. * @hide */ - @SystemApi + @SystemApi(client = MODULE_LIBRARIES) public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; /** @@ -986,7 +986,7 @@ public class ConnectivityManager { * if no such network is available. * @hide */ - @SystemApi + @SystemApi(client = MODULE_LIBRARIES) public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; /** @hide */ From 10774b7ebc9e116d7a75076f3d39f19d3dc88468 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Wed, 17 Mar 2021 14:16:01 +0800 Subject: [PATCH 109/232] Expose systemReady for SystemServer SystemServer cannot call the hidden API of ConnectivityManager after it becomes a part of mainline module. So expose the hidden API for SystemServer. Bug: 182963354 Test: m services Change-Id: I9c1dc8eb1401dbdc069d5c9fc3992f4c7939b70e --- framework/api/module-lib-current.txt | 1 + framework/src/android/net/ConnectivityManager.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index c3b1800af2..05e0813395 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -12,6 +12,7 @@ package android.net { method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); + method public void systemReady(); field public static final String PRIVATE_DNS_MODE_OFF = "off"; field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 8b669284ad..dc06cd96ef 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -915,8 +915,8 @@ public class ConnectivityManager { /** * @hide - * TODO: Expose for SystemServer when becomes a module. */ + @SystemApi(client = MODULE_LIBRARIES) public void systemReady() { try { mService.systemReady(); From debfe6062a440bc45d27359bacc76312bbecfc24 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Wed, 17 Mar 2021 14:53:35 +0800 Subject: [PATCH 110/232] Modify the comment of getPrivateDnsMode Bug: 172183305 Test: m Change-Id: I2f1b44cf2a362b42f052ea5d34a5cec03d46e661 --- framework/src/android/net/ConnectivityManager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 9cd3179d25..6c989a0b72 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5203,8 +5203,7 @@ public class ConnectivityManager { /** * Get private DNS mode from settings. * - * @param context The Context to get its ContentResolver to query the private DNS mode from - * settings. + * @param context The Context to query the private DNS mode from settings. * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants. * * @hide From 845456ebdce5d2089ca953808d952be1ecf64765 Mon Sep 17 00:00:00 2001 From: paulhu Date: Wed, 17 Mar 2021 17:19:09 +0800 Subject: [PATCH 111/232] Add ConnectivitySettingsManager This class is used to manager the connectivity module related settings. Bug: 182538166 Test: make Change-Id: I5e02e719ce0d305d7c8a45fefb850d7b981f07eb --- .../net/ConnectivitySettingsManager.java | 46 +++++++++++++++++++ .../net/util/MultinetworkPolicyTracker.java | 8 ++-- 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 framework/src/android/net/ConnectivitySettingsManager.java diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java new file mode 100644 index 0000000000..d454365452 --- /dev/null +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -0,0 +1,46 @@ +/* + * 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 android.net; + +/** + * A manager class for connectivity module settings. + * + * @hide + */ +public class ConnectivitySettingsManager { + + private ConnectivitySettingsManager() {} + + /** + * Whether to automatically switch away from wifi networks that lose Internet access. + * Only meaningful if config_networkAvoidBadWifi is set to 0, otherwise the system always + * avoids such networks. Valid values are: + * + * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013. + * null: Ask the user whether to switch away from bad wifi. + * 1: Avoid bad wifi. + */ + public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi"; + + /** + * User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be + * overridden by the system based on device or application state. If null, the value + * specified by config_networkMeteredMultipathPreference is used. + */ + public static final String NETWORK_METERED_MULTIPATH_PREFERENCE = + "network_metered_multipath_preference"; +} diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java index 739ddada50..6a49aa2576 100644 --- a/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -16,8 +16,8 @@ package android.net.util; -import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; -import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; +import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI; +import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE; import android.annotation.NonNull; import android.content.BroadcastReceiver; @@ -110,8 +110,8 @@ public class MultinetworkPolicyTracker { mHandler = handler; mAvoidBadWifiCallback = avoidBadWifiCallback; mSettingsUris = Arrays.asList( - Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), - Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); + Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), + Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); mResolver = mContext.getContentResolver(); mSettingObserver = new SettingObserver(); mBroadcastReceiver = new BroadcastReceiver() { From 5b9dd873332e24a8ffb09e0d2c2c6c1a73cf56a3 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 17 Mar 2021 10:57:31 +0000 Subject: [PATCH 112/232] Remove extra comment This fixes a merge conflict with downstream branches due to an incomplete cherry-pick. Bug: 171540887 Test: m Change-Id: I27a8f20f1a1d83b472700648f3f5a68413a76ac3 Merged-In: I7432fe4c87cd3cab04dcb6185c9a4f3f84376549 --- framework/Android.bp | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/Android.bp b/framework/Android.bp index 9da27d2711..86b85e8398 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -23,7 +23,6 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -// TODO: use a java_library in the bootclasspath instead filegroup { name: "framework-connectivity-internal-sources", srcs: [ From 1b9f03a190c6fb83ee1689563153105ac919a28f Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 12 Mar 2021 15:24:06 +0900 Subject: [PATCH 113/232] Remove Protocol.BASE_* usage in Connectivity ConnectivityManager and NetworkAgent do not share their handler with any other component, so there is no reason to use addresses that do not overlap. Protocol.BASE_* was written to allow for interaction "between different StateMachine implementations without a conflict", but the classes do not use StateMachine, and they do not have such interactions. Bug: 177046265 Test: atest FrameworksNetTests Change-Id: I18c341d4a2c01cb9559d682a9ad1ff259e6b5855 --- .../src/android/net/ConnectivityManager.java | 24 +++++++++---------- framework/src/android/net/NetworkAgent.java | 6 +++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 9cd3179d25..d6ca8be48f 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -77,7 +77,6 @@ import android.util.SparseIntArray; import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; -import com.android.internal.util.Protocol; import libcore.net.event.NetworkEventDispatcher; @@ -3551,29 +3550,28 @@ public class ConnectivityManager { } } - private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER; /** @hide */ - public static final int CALLBACK_PRECHECK = BASE + 1; + public static final int CALLBACK_PRECHECK = 1; /** @hide */ - public static final int CALLBACK_AVAILABLE = BASE + 2; + public static final int CALLBACK_AVAILABLE = 2; /** @hide arg1 = TTL */ - public static final int CALLBACK_LOSING = BASE + 3; + public static final int CALLBACK_LOSING = 3; /** @hide */ - public static final int CALLBACK_LOST = BASE + 4; + public static final int CALLBACK_LOST = 4; /** @hide */ - public static final int CALLBACK_UNAVAIL = BASE + 5; + public static final int CALLBACK_UNAVAIL = 5; /** @hide */ - public static final int CALLBACK_CAP_CHANGED = BASE + 6; + public static final int CALLBACK_CAP_CHANGED = 6; /** @hide */ - public static final int CALLBACK_IP_CHANGED = BASE + 7; + public static final int CALLBACK_IP_CHANGED = 7; /** @hide obj = NetworkCapabilities, arg1 = seq number */ - private static final int EXPIRE_LEGACY_REQUEST = BASE + 8; + private static final int EXPIRE_LEGACY_REQUEST = 8; /** @hide */ - public static final int CALLBACK_SUSPENDED = BASE + 9; + public static final int CALLBACK_SUSPENDED = 9; /** @hide */ - public static final int CALLBACK_RESUMED = BASE + 10; + public static final int CALLBACK_RESUMED = 10; /** @hide */ - public static final int CALLBACK_BLK_CHANGED = BASE + 11; + public static final int CALLBACK_BLK_CHANGED = 11; /** @hide */ public static String getCallbackName(int whichCallback) { diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index b3ab0ee8bd..a127c6f6de 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -37,7 +37,6 @@ import android.util.Log; import com.android.connectivity.aidl.INetworkAgent; import com.android.connectivity.aidl.INetworkAgentRegistry; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Protocol; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -125,7 +124,10 @@ public abstract class NetworkAgent { */ public final int providerId; - private static final int BASE = Protocol.BASE_NETWORK_AGENT; + // ConnectivityService parses message constants from itself and NetworkAgent with MessageUtils + // for debugging purposes, and crashes if some messages have the same values. + // TODO: have ConnectivityService store message names in different maps and remove this base + private static final int BASE = 200; /** * Sent by ConnectivityService to the NetworkAgent to inform it of From 92ebd70044634ddb502dd8bd999e8e528e68d04d Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Mon, 22 Feb 2021 18:36:38 +0800 Subject: [PATCH 114/232] Replace the usage of UidRange UidRange is used in a shared way between ConnectivityService and VPN through the use of NetworkCapabilities. UidRange will be part of the ConnectivityService mainline but Vpn.java will stay in the framework. We need a way to replace the APIs using UidRange, or to make UidRange system API. The only really relevant surface here is NetworkCapabilities#{setUids, getUids}. The need for UidRange could be replaced by an integer Range, so replace the usage of UidRange by a integer Range in NetworkCapabilities#{setUids, getUids} and update the relevant callers. Bug: 172183305 Test: atest FrameworksNetTests CtsNetTestCasesLatestSdk Change-Id: I4e5aec6ef1ea02e038fcd7ed117a3b67b69c5cb9 --- .../src/android/net/NetworkCapabilities.java | 31 ++++++++++++------- framework/src/android/net/NetworkRequest.java | 5 +-- framework/src/android/net/UidRange.java | 31 +++++++++++++++++++ 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 058f3c999d..ba9f21b261 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -32,6 +32,7 @@ import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; import android.util.ArraySet; +import android.util.Range; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -153,7 +154,7 @@ public final class NetworkCapabilities implements Parcelable { setTransportInfo(null); } mSignalStrength = nc.mSignalStrength; - setUids(nc.mUids); // Will make the defensive copy + mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids); setAdministratorUids(nc.getAdministratorUids()); mOwnerUid = nc.mOwnerUid; mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; @@ -1458,9 +1459,8 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public @NonNull NetworkCapabilities setSingleUid(int uid) { - final ArraySet identity = new ArraySet<>(1); - identity.add(new UidRange(uid, uid)); - setUids(identity); + mUids = new ArraySet<>(1); + mUids.add(new UidRange(uid, uid)); return this; } @@ -1469,12 +1469,8 @@ public final class NetworkCapabilities implements Parcelable { * This makes a copy of the set so that callers can't modify it after the call. * @hide */ - public @NonNull NetworkCapabilities setUids(Set uids) { - if (null == uids) { - mUids = null; - } else { - mUids = new ArraySet<>(uids); - } + public @NonNull NetworkCapabilities setUids(@Nullable Set> uids) { + mUids = UidRange.fromIntRanges(uids); return this; } @@ -1483,8 +1479,19 @@ public final class NetworkCapabilities implements Parcelable { * This returns a copy of the set so that callers can't modify the original object. * @hide */ - public @Nullable Set getUids() { - return null == mUids ? null : new ArraySet<>(mUids); + public @Nullable Set> getUids() { + return UidRange.toIntRanges(mUids); + } + + /** + * Get the list of UIDs this network applies to. + * This returns a copy of the set so that callers can't modify the original object. + * @hide + */ + public @Nullable Set getUidRanges() { + if (mUids == null) return null; + + return new ArraySet<>(mUids); } /** diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index dbe3ecc4d7..4ebbf06c51 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -45,6 +45,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; +import android.util.Range; import android.util.proto.ProtoOutputStream; import java.util.Arrays; @@ -277,11 +278,11 @@ public class NetworkRequest implements Parcelable { * Set the watched UIDs for this request. This will be reset and wiped out unless * the calling app holds the CHANGE_NETWORK_STATE permission. * - * @param uids The watched UIDs as a set of UidRanges, or null for everything. + * @param uids The watched UIDs as a set of {@code Range}, or null for everything. * @return The builder to facilitate chaining. * @hide */ - public Builder setUids(Set uids) { + public Builder setUids(@Nullable Set> uids) { mNetworkCapabilities.setUids(uids); return this; } diff --git a/framework/src/android/net/UidRange.java b/framework/src/android/net/UidRange.java index 26518d32ed..bc67c745c9 100644 --- a/framework/src/android/net/UidRange.java +++ b/framework/src/android/net/UidRange.java @@ -20,8 +20,11 @@ import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Range; import java.util.Collection; +import java.util.Set; /** * An inclusive range of UIDs. @@ -149,4 +152,32 @@ public final class UidRange implements Parcelable { } return false; } + + /** + * Convert a set of {@code Range} to a set of {@link UidRange}. + */ + @Nullable + public static ArraySet fromIntRanges(@Nullable Set> ranges) { + if (null == ranges) return null; + + final ArraySet uids = new ArraySet<>(); + for (Range range : ranges) { + uids.add(new UidRange(range.getLower(), range.getUpper())); + } + return uids; + } + + /** + * Convert a set of {@link UidRange} to a set of {@code Range}. + */ + @Nullable + public static ArraySet> toIntRanges(@Nullable Set ranges) { + if (null == ranges) return null; + + final ArraySet> uids = new ArraySet<>(); + for (UidRange range : ranges) { + uids.add(new Range(range.start, range.stop)); + } + return uids; + } } From 0c32e09028fffb8fb0008a3d86561f2bedc1d6c9 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Thu, 11 Mar 2021 16:13:13 +0800 Subject: [PATCH 115/232] Expose uids related APIs in NetworkRequest and NetworkCapabilities NetworkRequest is moving into the incoming connectivity mainline module. The hidden setUids becomes inaccessible outside the module. Shims for support cts in different API levels will need to use it to verify the behavior of NetworkRequest. Thus, expose it to the API surface. Also, VPN uses getUids and setUids to control network capabilities. Networkcapabilities is a part of incoming connectivity mainline module but VPN is not. Thus, exposing these two methods are needed to allow VPN to continue using it. Test: make update-api Bug: 172183305 Change-Id: I107c329d4d7130d488772166eae8b5e7aaa2ff04 --- framework/api/module-lib-current.txt | 9 ++++++++ .../src/android/net/NetworkCapabilities.java | 21 +++++++++++++++++++ framework/src/android/net/NetworkRequest.java | 4 ++++ 3 files changed, 34 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index bb296476c7..6339094341 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -27,9 +27,18 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { + method @Nullable public java.util.Set> getUids(); field public static final int TRANSPORT_TEST = 7; // 0x7 } + public static final class NetworkCapabilities.Builder { + method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set>); + } + + public static class NetworkRequest.Builder { + method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set>); + } + public class ParseException extends java.lang.RuntimeException { ctor public ParseException(@NonNull String); ctor public ParseException(@NonNull String, @NonNull Throwable); diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index ba9f21b261..182bc7141a 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.ConnectivityManager.NetworkCallback; @@ -1477,8 +1478,13 @@ public final class NetworkCapabilities implements Parcelable { /** * Get the list of UIDs this network applies to. * This returns a copy of the set so that callers can't modify the original object. + * + * @return the list of UIDs this network applies to. If {@code null}, then the network applies + * to all UIDs. * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NullableCollection") public @Nullable Set> getUids() { return UidRange.toIntRanges(mUids); } @@ -2661,6 +2667,21 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Set the list of UIDs this network applies to. + * + * @param uids the list of UIDs this network applies to, or {@code null} if this network + * applies to all UIDs. + * @return this builder + * @hide + */ + @NonNull + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public Builder setUids(@Nullable Set> uids) { + mCaps.setUids(uids); + return this; + } + /** * Builds the instance of the capabilities. * diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 4ebbf06c51..cf131f0df6 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -36,6 +36,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.NetworkCapabilities.NetCapability; @@ -282,6 +283,9 @@ public class NetworkRequest implements Parcelable { * @return The builder to facilitate chaining. * @hide */ + @NonNull + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("MissingGetterMatchingBuilder") public Builder setUids(@Nullable Set> uids) { mNetworkCapabilities.setUids(uids); return this; From eb6aa22205385bf334031382d17357f597a01ee6 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Sun, 14 Mar 2021 12:56:26 +0900 Subject: [PATCH 116/232] Add framework-connectivity.impl The library will be included in the connectivity module APEX when migrating its sources out of framework-minus-apex. Bug: 171540887 Test: m Change-Id: I1595521eaced6e6997c076bb56b06ffdd22a4fa0 --- framework/Android.bp | 35 +++++++++++++++++++++++++++++++++++ framework/jarjar-rules.txt | 7 +++++++ 2 files changed, 42 insertions(+) create mode 100644 framework/jarjar-rules.txt diff --git a/framework/Android.bp b/framework/Android.bp index 86b85e8398..86433e1c38 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -83,3 +83,38 @@ java_sdk_library { ], permitted_packages: ["android.net", "com.android.connectivity.aidl"], } + +java_library { + name: "framework-connectivity.impl", + // Instead of building against private API (framework.jar), + // build against core_platform + framework-minus-apex + module + // stub libs. This allows framework.jar to depend on this library, + // so it can be part of the private API until all clients have been migrated. + // TODO: just build against module_api, and remove this jar from + // the private API. + sdk_version: "core_platform", + srcs: [ + ":framework-connectivity-sources", + ], + aidl: { + include_dirs: [ + "frameworks/base/core/java", // For framework parcelables + "frameworks/native/aidl/binder", // For PersistableBundle.aidl + ], + }, + libs: [ + "framework-minus-apex", + // TODO: just framework-tethering, framework-wifi when building against module_api + "framework-tethering.stubs.module_lib", + "framework-wifi.stubs.module_lib", + "unsupportedappusage", + "ServiceConnectivityResources", + ], + static_libs: [ + "net-utils-device-common", + ], + jarjar_rules: "jarjar-rules.txt", + apex_available: ["com.android.tethering"], + installable: true, + permitted_packages: ["android.net", "com.android.connectivity.aidl"], +} diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt new file mode 100644 index 0000000000..381a4ac875 --- /dev/null +++ b/framework/jarjar-rules.txt @@ -0,0 +1,7 @@ +rule com.android.net.module.util.** android.net.connectivity.framework.util.@1 + +# TODO (b/149403767): remove the annotations from net-utils-device-common instead of here +zap android.annotation.** +zap com.android.net.module.annotation.** +zap com.android.internal.annotations.** + From f9294e7cd1a9bd3772a66636c0f181b486052dd3 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Thu, 18 Mar 2021 09:44:34 +0800 Subject: [PATCH 117/232] Expose APIs for Settings ConnectivityManager will be a part of incoming connectivity mainline. Settings will no longer to access the hidden methods. For those methods that accept the interaction from users, they should be exposed as formal interface to allow the functionality. Expose them to API surface. Bug: 172183305 Test: make update-api Change-Id: Id4533b94291766bb060af0091b5ccb81a00630fd --- framework/api/module-lib-current.txt | 6 +++ .../src/android/net/ConnectivityManager.java | 38 +++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index bb296476c7..f4469417e3 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -6,11 +6,17 @@ package android.net { } public class ConnectivityManager { + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset(); method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshot(); + method @Nullable public android.net.ProxyInfo getGlobalProxy(); method @NonNull public static android.util.Range getIpSecNetIdRange(); method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network); + method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); field public static final String PRIVATE_DNS_MODE_OFF = "off"; diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index a621233e32..0434f32ea4 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3038,8 +3038,9 @@ public class ConnectivityManager { * HTTP proxy. A {@code null} value will clear the global HTTP proxy. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.NETWORK_STACK) - public void setGlobalProxy(ProxyInfo p) { + public void setGlobalProxy(@Nullable ProxyInfo p) { try { mService.setGlobalProxy(p); } catch (RemoteException e) { @@ -3054,6 +3055,8 @@ public class ConnectivityManager { * if no global HTTP proxy is set. * @hide */ + @SystemApi(client = MODULE_LIBRARIES) + @Nullable public ProxyInfo getGlobalProxy() { try { return mService.getGlobalProxy(); @@ -4388,8 +4391,13 @@ public class ConnectivityManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) - public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + public void setAcceptUnvalidated(@NonNull Network network, boolean accept, boolean always) { try { mService.setAcceptUnvalidated(network, accept, always); } catch (RemoteException e) { @@ -4411,8 +4419,14 @@ public class ConnectivityManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.NETWORK_STACK) - public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) { + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + public void setAcceptPartialConnectivity(@NonNull Network network, boolean accept, + boolean always) { try { mService.setAcceptPartialConnectivity(network, accept, always); } catch (RemoteException e) { @@ -4430,8 +4444,13 @@ public class ConnectivityManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) - public void setAvoidUnvalidated(Network network) { + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) + public void setAvoidUnvalidated(@NonNull Network network) { try { mService.setAvoidUnvalidated(network); } catch (RemoteException e) { @@ -4561,7 +4580,10 @@ public class ConnectivityManager { * Resets all connectivity manager settings back to factory defaults. * @hide */ - @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset() { try { mService.factoryReset(); From c9925e096be99759f500698709f5e0c684c4fe31 Mon Sep 17 00:00:00 2001 From: paulhu Date: Wed, 17 Mar 2021 20:30:33 +0800 Subject: [PATCH 118/232] Add more connectivity module settings Add more connectivity module settings and update all references to ConnectivitySettingsManager. Bug: 182538166 Test: atest FrameworksNetTests Change-Id: Ie96fbd0996ed3acb37099b6270bf3d4c2e558e9a --- .../src/android/net/ConnectivityManager.java | 4 +- .../net/ConnectivitySettingsManager.java | 195 ++++++++++++++++++ 2 files changed, 197 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 8b669284ad..b350bc2527 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -16,6 +16,8 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE; import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.REQUEST; @@ -23,8 +25,6 @@ import static android.net.NetworkRequest.Type.TRACK_BEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; -import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE; -import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import android.annotation.CallbackExecutor; import android.annotation.IntDef; diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index d454365452..bbd83931ee 100644 --- a/framework/src/android/net/ConnectivitySettingsManager.java +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -16,6 +16,11 @@ package android.net; +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A manager class for connectivity module settings. * @@ -25,6 +30,196 @@ public class ConnectivitySettingsManager { private ConnectivitySettingsManager() {} + /** Data activity timeout settings */ + + /** + * Inactivity timeout to track mobile data activity. + * + * If set to a positive integer, it indicates the inactivity timeout value in seconds to + * infer the data activity of mobile network. After a period of no activity on mobile + * networks with length specified by the timeout, an {@code ACTION_DATA_ACTIVITY_CHANGE} + * intent is fired to indicate a transition of network status from "active" to "idle". Any + * subsequent activity on mobile networks triggers the firing of {@code + * ACTION_DATA_ACTIVITY_CHANGE} intent indicating transition from "idle" to "active". + * + * Network activity refers to transmitting or receiving data on the network interfaces. + * + * Tracking is disabled if set to zero or negative value. + */ + public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile"; + + /** + * Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE} + * but for Wifi network. + */ + public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi"; + + /** Dns resolver settings */ + + /** + * Sample validity in seconds to configure for the system DNS resolver. + */ + public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS = + "dns_resolver_sample_validity_seconds"; + + /** + * Success threshold in percent for use with the system DNS resolver. + */ + public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT = + "dns_resolver_success_threshold_percent"; + + /** + * Minimum number of samples needed for statistics to be considered meaningful in the + * system DNS resolver. + */ + public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples"; + + /** + * Maximum number taken into account for statistics purposes in the system DNS resolver. + */ + public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples"; + + /** Network switch notification settings */ + + /** + * The maximum number of notifications shown in 24 hours when switching networks. + */ + public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT = + "network_switch_notification_daily_limit"; + + /** + * The minimum time in milliseconds between notifications when switching networks. + */ + public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS = + "network_switch_notification_rate_limit_millis"; + + /** Captive portal settings */ + + /** + * The URL used for HTTP captive portal detection upon a new connection. + * A 204 response code from the server is used for validation. + */ + public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url"; + + /** + * What to do when connecting a network that presents a captive portal. + * Must be one of the CAPTIVE_PORTAL_MODE_* constants above. + * + * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT. + */ + public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode"; + + /** + * Don't attempt to detect captive portals. + */ + public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; + + /** + * When detecting a captive portal, display a notification that + * prompts the user to sign in. + */ + public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; + + /** + * When detecting a captive portal, immediately disconnect from the + * network and do not reconnect to that network in the future. + */ + public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + CAPTIVE_PORTAL_MODE_IGNORE, + CAPTIVE_PORTAL_MODE_PROMPT, + CAPTIVE_PORTAL_MODE_AVOID, + }) + public @interface CaptivePortalMode {} + + /** Global http proxy settings */ + + /** + * Host name for global http proxy. Set via ConnectivityManager. + */ + public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host"; + + /** + * Integer host port for global http proxy. Set via ConnectivityManager. + */ + public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port"; + + /** + * Exclusion list for global proxy. This string contains a list of + * comma-separated domains where the global proxy does not apply. + * Domains should be listed in a comma- separated list. Example of + * acceptable formats: ".domain1.com,my.domain2.com" Use + * ConnectivityManager to set/get. + */ + public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST = + "global_http_proxy_exclusion_list"; + + /** + * The location PAC File for the proxy. + */ + public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url"; + + /** Private dns settings */ + + /** + * The requested Private DNS mode (string), and an accompanying specifier (string). + * + * Currently, the specifier holds the chosen provider name when the mode requests + * a specific provider. It may be used to store the provider name even when the + * mode changes so that temporarily disabling and re-enabling the specific + * provider mode does not necessitate retyping the provider hostname. + */ + public static final String PRIVATE_DNS_MODE = "private_dns_mode"; + + /** + * The specific Private DNS provider name. + */ + public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier"; + + /** + * Forced override of the default mode (hardcoded as "automatic", nee "opportunistic"). + * This allows changing the default mode without effectively disabling other modes, + * all of which require explicit user action to enable/configure. See also b/79719289. + * + * Value is a string, suitable for assignment to PRIVATE_DNS_MODE above. + */ + public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode"; + + /** Other settings */ + + /** + * The number of milliseconds to hold on to a PendingIntent based request. This delay gives + * the receivers of the PendingIntent an opportunity to make a new network request before + * the Network satisfying the request is potentially removed. + */ + public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS = + "connectivity_release_pending_intent_delay_ms"; + + /** + * Whether the mobile data connection should remain active even when higher + * priority networks like WiFi are active, to help make network switching faster. + * + * See ConnectivityService for more info. + * + * (0 = disabled, 1 = enabled) + */ + public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; + + /** + * Whether the wifi data connection should remain active even when higher + * priority networks like Ethernet are active, to keep both networks. + * In the case where higher priority networks are connected, wifi will be + * unused unless an application explicitly requests to use it. + * + * See ConnectivityService for more info. + * + * (0 = disabled, 1 = enabled) + */ + public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested"; + /** * Whether to automatically switch away from wifi networks that lose Internet access. * Only meaningful if config_networkAvoidBadWifi is set to 0, otherwise the system always From d8bc0faf7da4cde13ba91a838feb4fc29b6605dd Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 11 Mar 2021 15:41:52 +0900 Subject: [PATCH 119/232] Add SystemMessages protos to Connectivity This follows the model used for the same protos by the Wifi module in service-wifi. SystemMessageProto is used by NetworkNotificationManager to define IDs for notifications that are also counted in system notification metrics. Bug: 171860710 Test: m Change-Id: Iec674913fad9ad59c04249714a08b5a0d8ab0f84 --- service/Android.bp | 16 ++++++++++++++++ service/jarjar-rules.txt | 3 +++ service/proto/connectivityproto.proto | 20 ++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 service/proto/connectivityproto.proto diff --git a/service/Android.bp b/service/Android.bp index f630ceac36..1330e719e7 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -68,6 +68,7 @@ java_library { "net-utils-framework-common", "netd-client", "PlatformProperties", + "service-connectivity-protos", ], apex_available: [ "//apex_available:platform", @@ -75,6 +76,21 @@ java_library { ], } +java_library { + name: "service-connectivity-protos", + proto: { + type: "nano", + }, + srcs: [ + ":system-messages-proto-src", + ], + libs: ["libprotobuf-java-nano"], + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], +} + java_library { name: "service-connectivity", installable: true, diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt index a7b419b020..5caa11b11a 100644 --- a/service/jarjar-rules.txt +++ b/service/jarjar-rules.txt @@ -12,3 +12,6 @@ rule android.util.LocalLog* com.android.connectivity.util.LocalLog@1 # the one in com.android.internal.util rule android.util.IndentingPrintWriter* android.connectivity.util.IndentingPrintWriter@1 rule com.android.internal.util.** com.android.connectivity.util.@1 + +rule com.android.internal.messages.** com.android.connectivity.messages.@1 +rule com.google.protobuf.** com.android.connectivity.protobuf.@1 diff --git a/service/proto/connectivityproto.proto b/service/proto/connectivityproto.proto new file mode 100644 index 0000000000..a992d7c263 --- /dev/null +++ b/service/proto/connectivityproto.proto @@ -0,0 +1,20 @@ +/* + * 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. + */ + +syntax = "proto2"; + +// Connectivity protos can be created in this directory. Note this file must be included before +// building system-messages-proto, otherwise it will not build by itself. From 8e0c1f0850a2f2f830294bb20d583c716f908a2a Mon Sep 17 00:00:00 2001 From: lucaslin Date: Thu, 18 Mar 2021 20:30:22 +0800 Subject: [PATCH 120/232] Expose getCapabilities Expose getCapabilities() for EthernetNetworkFactory. Bug: 182963415 Test: m ethernet-service Change-Id: I430416af8fae3d4ee99f2f2abe529e3051e9e5cb --- framework/api/current.txt | 1 + framework/src/android/net/NetworkCapabilities.java | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index f22d4b7b77..e415e01fea 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -291,6 +291,7 @@ package android.net { ctor public NetworkCapabilities(); ctor public NetworkCapabilities(android.net.NetworkCapabilities); method public int describeContents(); + method @NonNull public int[] getCapabilities(); method public int getLinkDownstreamBandwidthKbps(); method public int getLinkUpstreamBandwidthKbps(); method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 058f3c999d..5ec7aa1b23 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -609,10 +609,8 @@ public final class NetworkCapabilities implements Parcelable { * Gets all the capabilities set on this {@code NetworkCapability} instance. * * @return an array of capability values for this instance. - * @hide */ - @UnsupportedAppUsage - public @NetCapability int[] getCapabilities() { + public @NonNull @NetCapability int[] getCapabilities() { return NetworkCapabilitiesUtils.unpackBits(mNetworkCapabilities); } From 8238a7665a0222e11289c222f83bf62210bc5d27 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 16 Mar 2021 18:06:06 +0900 Subject: [PATCH 121/232] Add startCaptivePortalApp to system API The API is already used by settings and should be usable by setup wizards. It is the only way for a caller outside of the system_server to trigger the captive portal application. The API is already CTS tested in android.net.cts.CaptivePortalTest. Fixes: 182871577 Test: atest CtsNetTestCases:android.net.cts.CaptivePortalTest Change-Id: Ie8d9a546b54524ba837715baa94a07d1f993d8d3 --- framework/api/module-lib-current.txt | 1 + framework/src/android/net/ConnectivityManager.java | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index ab039165ad..37312fa79d 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -19,6 +19,7 @@ package android.net { method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network); method public void systemReady(); field public static final String PRIVATE_DNS_MODE_OFF = "off"; field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 786ed240e3..fe05f3a9de 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4461,12 +4461,20 @@ public class ConnectivityManager { /** * Requests that the system open the captive portal app on the specified network. * + *

    This is to be used on networks where a captive portal was detected, as per + * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}. + * * @param network The network to log into. * * @hide */ - @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) - public void startCaptivePortalApp(Network network) { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void startCaptivePortalApp(@NonNull Network network) { try { mService.startCaptivePortalApp(network); } catch (RemoteException e) { From 342ddddd9dbe2ee06daede95ff7bec81d5226e65 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 18 Mar 2021 23:27:19 +0900 Subject: [PATCH 122/232] Add InetAddressCompat Although the InetAddress symbols used by Connectivity are stable core platform API, and should be usable, the core_current stubs are not yet part of the module_current API. Until that is fixed, add an InetAddressCompat utility that calls the three static methods by reflection. Test: atest FrameworksNetTests CtsNetTestCases Bug: 183097033 Change-Id: I797009aeff1d39ae2dc06ef69d2e235689b43c89 --- .../src/android/net/ConnectivityManager.java | 2 +- .../src/android/net/InetAddressCompat.java | 76 +++++++++++++++++++ framework/src/android/net/Network.java | 4 +- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 framework/src/android/net/InetAddressCompat.java diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index a621233e32..dcb80e6240 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4639,7 +4639,7 @@ public class ConnectivityManager { Log.e(TAG, "Can't set proxy properties", e); } // Must flush DNS cache as new network may have different DNS resolutions. - InetAddress.clearDnsCache(); + InetAddressCompat.clearDnsCache(); // Must flush socket pool as idle sockets will be bound to previous network and may // cause subsequent fetches to be performed on old network. NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged(); diff --git a/framework/src/android/net/InetAddressCompat.java b/framework/src/android/net/InetAddressCompat.java new file mode 100644 index 0000000000..8404441de6 --- /dev/null +++ b/framework/src/android/net/InetAddressCompat.java @@ -0,0 +1,76 @@ +/* + * 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 android.net; + +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Compatibility utility for InetAddress core platform APIs. + * + * Connectivity has access to such APIs, but they are not part of the module_current stubs yet + * (only core_current). Most stable core platform APIs are included manually in the connectivity + * build rules, but because InetAddress is also part of the base java SDK that is earlier on the + * classpath, the extra core platform APIs are not seen. + * + * TODO (b/183097033): remove this utility as soon as core_current is part of module_current + * @hide + */ +public class InetAddressCompat { + + /** + * @see InetAddress#clearDnsCache() + */ + public static void clearDnsCache() { + try { + InetAddress.class.getMethod("clearDnsCache").invoke(null); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + Log.wtf(InetAddressCompat.class.getSimpleName(), "Error clearing DNS cache", e); + } + } + + /** + * @see InetAddress#getAllByNameOnNet(String, int) + */ + public static InetAddress[] getAllByNameOnNet(String host, int netId) throws + UnknownHostException { + try { + return (InetAddress[]) InetAddress.class.getMethod("getAllByNameOnNet", + String.class, int.class).invoke(null, host, netId); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling getAllByNameOnNet", e); + throw new IllegalStateException("Error querying via getAllNameOnNet", e); + } + } + + /** + * @see InetAddress#getByNameOnNet(String, int) + */ + public static InetAddress getByNameOnNet(String host, int netId) throws + UnknownHostException { + try { + return (InetAddress) InetAddress.class.getMethod("getByNameOnNet", + String.class, int.class).invoke(null, host, netId); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling getAllByNameOnNet", e); + throw new IllegalStateException("Error querying via getByNameOnNet", e); + } + } +} diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java index 7245db3b17..0741414ab3 100644 --- a/framework/src/android/net/Network.java +++ b/framework/src/android/net/Network.java @@ -142,7 +142,7 @@ public class Network implements Parcelable { * @throws UnknownHostException if the address lookup fails. */ public InetAddress[] getAllByName(String host) throws UnknownHostException { - return InetAddress.getAllByNameOnNet(host, getNetIdForResolv()); + return InetAddressCompat.getAllByNameOnNet(host, getNetIdForResolv()); } /** @@ -155,7 +155,7 @@ public class Network implements Parcelable { * if the address lookup fails. */ public InetAddress getByName(String host) throws UnknownHostException { - return InetAddress.getByNameOnNet(host, getNetIdForResolv()); + return InetAddressCompat.getByNameOnNet(host, getNetIdForResolv()); } /** From 5bfc990e4a8f77c59851266ae268ce2c98b9903e Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 18 Mar 2021 23:57:26 +0900 Subject: [PATCH 123/232] Move network selection utils to Connectivity NetworkScore, IOnCompleteListener should be in the Connectivity scope, as they are supporting classes for the ConnectivityManager APIs. Bug: 181512874 Test: m Change-Id: I6dc40a80e0bf5f86f5625b657b01eba969d41fcf --- .../aidl-export/android/net/NetworkScore.aidl | 20 ++++ .../src/android/net/IOnCompleteListener.aidl | 23 ++++ framework/src/android/net/NetworkScore.java | 108 ++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 framework/aidl-export/android/net/NetworkScore.aidl create mode 100644 framework/src/android/net/IOnCompleteListener.aidl create mode 100644 framework/src/android/net/NetworkScore.java diff --git a/framework/aidl-export/android/net/NetworkScore.aidl b/framework/aidl-export/android/net/NetworkScore.aidl new file mode 100644 index 0000000000..af12dcf7f1 --- /dev/null +++ b/framework/aidl-export/android/net/NetworkScore.aidl @@ -0,0 +1,20 @@ +/** + * 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 android.net; + +parcelable NetworkScore; + diff --git a/framework/src/android/net/IOnCompleteListener.aidl b/framework/src/android/net/IOnCompleteListener.aidl new file mode 100644 index 0000000000..4bb89f6c89 --- /dev/null +++ b/framework/src/android/net/IOnCompleteListener.aidl @@ -0,0 +1,23 @@ +/** + * + * 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 android.net; + +/** @hide */ +oneway interface IOnCompleteListener { + void onComplete(); +} diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java new file mode 100644 index 0000000000..f478010022 --- /dev/null +++ b/framework/src/android/net/NetworkScore.java @@ -0,0 +1,108 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Object representing the quality of a network as perceived by the user. + * + * A NetworkScore object represents the characteristics of a network that affects how good the + * network is considered for a particular use. + * @hide + */ +// TODO : @SystemApi when the implementation is complete +public final class NetworkScore implements Parcelable { + // This will be removed soon. Do *NOT* depend on it for any new code that is not part of + // a migration. + private final int mLegacyInt; + + /** @hide */ + NetworkScore(final int legacyInt) { + this.mLegacyInt = legacyInt; + } + + private NetworkScore(@NonNull final Parcel in) { + mLegacyInt = in.readInt(); + } + + public int getLegacyInt() { + return mLegacyInt; + } + + @Override + public String toString() { + return "Score(" + mLegacyInt + ")"; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(mLegacyInt); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull public static final Creator CREATOR = new Creator<>() { + @Override + @NonNull + public NetworkScore createFromParcel(@NonNull final Parcel in) { + return new NetworkScore(in); + } + + @Override + @NonNull + public NetworkScore[] newArray(int size) { + return new NetworkScore[size]; + } + }; + + /** + * A builder for NetworkScore. + */ + public static final class Builder { + private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE; + private int mLegacyInt = INVALID_LEGACY_INT; + + /** + * Sets the legacy int for this score. + * + * Do not rely on this. It will be gone by the time S is released. + * + * @param score the legacy int + * @return this + */ + @NonNull + public Builder setLegacyInt(final int score) { + mLegacyInt = score; + return this; + } + + /** + * Builds this NetworkScore. + * @return The built NetworkScore object. + */ + @NonNull + public NetworkScore build() { + return new NetworkScore(mLegacyInt); + } + } +} From fa6349a6c7ce3be0c2e26fae60913de9e8152e1e Mon Sep 17 00:00:00 2001 From: Anthony Stange Date: Thu, 18 Mar 2021 16:30:59 +0000 Subject: [PATCH 124/232] Revert "Expose uids related APIs in NetworkRequest and NetworkCa..." Revert "Add shims for NetworkRequest" Revert submission 1626206-replaceUidRange Reason for revert: Breaking build - b/183106405 Reverted Changes: I0b79c73e8:Add shims for NetworkRequest I4bc0daf5a:Replace the usage of UidRange I4e5aec6ef:Replace the usage of UidRange I107c329d4:Expose uids related APIs in NetworkRequest and Net... Change-Id: I45e08f89533af0d6851add38fecb5c6c114615ae --- framework/api/module-lib-current.txt | 9 -------- .../src/android/net/NetworkCapabilities.java | 21 ------------------- framework/src/android/net/NetworkRequest.java | 4 ---- 3 files changed, 34 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 6339094341..bb296476c7 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -27,18 +27,9 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { - method @Nullable public java.util.Set> getUids(); field public static final int TRANSPORT_TEST = 7; // 0x7 } - public static final class NetworkCapabilities.Builder { - method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set>); - } - - public static class NetworkRequest.Builder { - method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set>); - } - public class ParseException extends java.lang.RuntimeException { ctor public ParseException(@NonNull String); ctor public ParseException(@NonNull String, @NonNull Throwable); diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 182bc7141a..ba9f21b261 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -22,7 +22,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.ConnectivityManager.NetworkCallback; @@ -1478,13 +1477,8 @@ public final class NetworkCapabilities implements Parcelable { /** * Get the list of UIDs this network applies to. * This returns a copy of the set so that callers can't modify the original object. - * - * @return the list of UIDs this network applies to. If {@code null}, then the network applies - * to all UIDs. * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - @SuppressLint("NullableCollection") public @Nullable Set> getUids() { return UidRange.toIntRanges(mUids); } @@ -2667,21 +2661,6 @@ public final class NetworkCapabilities implements Parcelable { return this; } - /** - * Set the list of UIDs this network applies to. - * - * @param uids the list of UIDs this network applies to, or {@code null} if this network - * applies to all UIDs. - * @return this builder - * @hide - */ - @NonNull - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public Builder setUids(@Nullable Set> uids) { - mCaps.setUids(uids); - return this; - } - /** * Builds the instance of the capabilities. * diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index cf131f0df6..4ebbf06c51 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -36,7 +36,6 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.NetworkCapabilities.NetCapability; @@ -283,9 +282,6 @@ public class NetworkRequest implements Parcelable { * @return The builder to facilitate chaining. * @hide */ - @NonNull - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - @SuppressLint("MissingGetterMatchingBuilder") public Builder setUids(@Nullable Set> uids) { mNetworkCapabilities.setUids(uids); return this; From b6c51b2c978fbcb9c1507b61635c64229c8ffe53 Mon Sep 17 00:00:00 2001 From: Anthony Stange Date: Thu, 18 Mar 2021 16:30:59 +0000 Subject: [PATCH 125/232] Revert "Replace the usage of UidRange" Revert "Add shims for NetworkRequest" Revert submission 1626206-replaceUidRange Reason for revert: Breaking build - b/183106405 Reverted Changes: I0b79c73e8:Add shims for NetworkRequest I4bc0daf5a:Replace the usage of UidRange I4e5aec6ef:Replace the usage of UidRange I107c329d4:Expose uids related APIs in NetworkRequest and Net... Change-Id: I6290429db1c8e787f8138b55b98fd92a74ac6402 --- .../src/android/net/NetworkCapabilities.java | 31 +++++++------------ framework/src/android/net/NetworkRequest.java | 5 ++- framework/src/android/net/UidRange.java | 31 ------------------- 3 files changed, 14 insertions(+), 53 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index ba9f21b261..058f3c999d 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -32,7 +32,6 @@ import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; import android.util.ArraySet; -import android.util.Range; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -154,7 +153,7 @@ public final class NetworkCapabilities implements Parcelable { setTransportInfo(null); } mSignalStrength = nc.mSignalStrength; - mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids); + setUids(nc.mUids); // Will make the defensive copy setAdministratorUids(nc.getAdministratorUids()); mOwnerUid = nc.mOwnerUid; mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; @@ -1459,8 +1458,9 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public @NonNull NetworkCapabilities setSingleUid(int uid) { - mUids = new ArraySet<>(1); - mUids.add(new UidRange(uid, uid)); + final ArraySet identity = new ArraySet<>(1); + identity.add(new UidRange(uid, uid)); + setUids(identity); return this; } @@ -1469,8 +1469,12 @@ public final class NetworkCapabilities implements Parcelable { * This makes a copy of the set so that callers can't modify it after the call. * @hide */ - public @NonNull NetworkCapabilities setUids(@Nullable Set> uids) { - mUids = UidRange.fromIntRanges(uids); + public @NonNull NetworkCapabilities setUids(Set uids) { + if (null == uids) { + mUids = null; + } else { + mUids = new ArraySet<>(uids); + } return this; } @@ -1479,19 +1483,8 @@ public final class NetworkCapabilities implements Parcelable { * This returns a copy of the set so that callers can't modify the original object. * @hide */ - public @Nullable Set> getUids() { - return UidRange.toIntRanges(mUids); - } - - /** - * Get the list of UIDs this network applies to. - * This returns a copy of the set so that callers can't modify the original object. - * @hide - */ - public @Nullable Set getUidRanges() { - if (mUids == null) return null; - - return new ArraySet<>(mUids); + public @Nullable Set getUids() { + return null == mUids ? null : new ArraySet<>(mUids); } /** diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 4ebbf06c51..dbe3ecc4d7 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -45,7 +45,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; -import android.util.Range; import android.util.proto.ProtoOutputStream; import java.util.Arrays; @@ -278,11 +277,11 @@ public class NetworkRequest implements Parcelable { * Set the watched UIDs for this request. This will be reset and wiped out unless * the calling app holds the CHANGE_NETWORK_STATE permission. * - * @param uids The watched UIDs as a set of {@code Range}, or null for everything. + * @param uids The watched UIDs as a set of UidRanges, or null for everything. * @return The builder to facilitate chaining. * @hide */ - public Builder setUids(@Nullable Set> uids) { + public Builder setUids(Set uids) { mNetworkCapabilities.setUids(uids); return this; } diff --git a/framework/src/android/net/UidRange.java b/framework/src/android/net/UidRange.java index bc67c745c9..26518d32ed 100644 --- a/framework/src/android/net/UidRange.java +++ b/framework/src/android/net/UidRange.java @@ -20,11 +20,8 @@ import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; -import android.util.ArraySet; -import android.util.Range; import java.util.Collection; -import java.util.Set; /** * An inclusive range of UIDs. @@ -152,32 +149,4 @@ public final class UidRange implements Parcelable { } return false; } - - /** - * Convert a set of {@code Range} to a set of {@link UidRange}. - */ - @Nullable - public static ArraySet fromIntRanges(@Nullable Set> ranges) { - if (null == ranges) return null; - - final ArraySet uids = new ArraySet<>(); - for (Range range : ranges) { - uids.add(new UidRange(range.getLower(), range.getUpper())); - } - return uids; - } - - /** - * Convert a set of {@link UidRange} to a set of {@code Range}. - */ - @Nullable - public static ArraySet> toIntRanges(@Nullable Set ranges) { - if (null == ranges) return null; - - final ArraySet> uids = new ArraySet<>(); - for (UidRange range : ranges) { - uids.add(new Range(range.start, range.stop)); - } - return uids; - } } From 34ab07c7b37a1d896c71a615b6176c72a09ce4a4 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Fri, 19 Mar 2021 00:45:39 +0000 Subject: [PATCH 126/232] Revert^2 "Replace the usage of UidRange" b6c51b2c978fbcb9c1507b61635c64229c8ffe53 UidRange is used in a shared way between ConnectivityService and VPN through the use of NetworkCapabilities. UidRange will be part of the ConnectivityService mainline but Vpn.java will stay in the framework. We need a way to replace the APIs using UidRange, or to make UidRange system API. The only really relevant surface here is NetworkCapabilities#{setUids, getUids}. The need for UidRange could be replaced by an integer Range, so replace the usage of UidRange by a integer Range in NetworkCapabilities#{setUids, getUids} and update the relevant callers. Bug: 172183305 Test: atest FrameworksNetTests CtsNetTestCasesLatestSdk Change-Id: I0f679fb5fb8f4fe26461ca4912ca1fdfe7f43c9e Merged-In: I4e5aec6ef1ea02e038fcd7ed117a3b67b69c5cb9 --- .../src/android/net/NetworkCapabilities.java | 31 ++++++++++++------- framework/src/android/net/NetworkRequest.java | 5 +-- framework/src/android/net/UidRange.java | 31 +++++++++++++++++++ 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 058f3c999d..ba9f21b261 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -32,6 +32,7 @@ import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; import android.util.ArraySet; +import android.util.Range; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -153,7 +154,7 @@ public final class NetworkCapabilities implements Parcelable { setTransportInfo(null); } mSignalStrength = nc.mSignalStrength; - setUids(nc.mUids); // Will make the defensive copy + mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids); setAdministratorUids(nc.getAdministratorUids()); mOwnerUid = nc.mOwnerUid; mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; @@ -1458,9 +1459,8 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public @NonNull NetworkCapabilities setSingleUid(int uid) { - final ArraySet identity = new ArraySet<>(1); - identity.add(new UidRange(uid, uid)); - setUids(identity); + mUids = new ArraySet<>(1); + mUids.add(new UidRange(uid, uid)); return this; } @@ -1469,12 +1469,8 @@ public final class NetworkCapabilities implements Parcelable { * This makes a copy of the set so that callers can't modify it after the call. * @hide */ - public @NonNull NetworkCapabilities setUids(Set uids) { - if (null == uids) { - mUids = null; - } else { - mUids = new ArraySet<>(uids); - } + public @NonNull NetworkCapabilities setUids(@Nullable Set> uids) { + mUids = UidRange.fromIntRanges(uids); return this; } @@ -1483,8 +1479,19 @@ public final class NetworkCapabilities implements Parcelable { * This returns a copy of the set so that callers can't modify the original object. * @hide */ - public @Nullable Set getUids() { - return null == mUids ? null : new ArraySet<>(mUids); + public @Nullable Set> getUids() { + return UidRange.toIntRanges(mUids); + } + + /** + * Get the list of UIDs this network applies to. + * This returns a copy of the set so that callers can't modify the original object. + * @hide + */ + public @Nullable Set getUidRanges() { + if (mUids == null) return null; + + return new ArraySet<>(mUids); } /** diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index dbe3ecc4d7..4ebbf06c51 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -45,6 +45,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; +import android.util.Range; import android.util.proto.ProtoOutputStream; import java.util.Arrays; @@ -277,11 +278,11 @@ public class NetworkRequest implements Parcelable { * Set the watched UIDs for this request. This will be reset and wiped out unless * the calling app holds the CHANGE_NETWORK_STATE permission. * - * @param uids The watched UIDs as a set of UidRanges, or null for everything. + * @param uids The watched UIDs as a set of {@code Range}, or null for everything. * @return The builder to facilitate chaining. * @hide */ - public Builder setUids(Set uids) { + public Builder setUids(@Nullable Set> uids) { mNetworkCapabilities.setUids(uids); return this; } diff --git a/framework/src/android/net/UidRange.java b/framework/src/android/net/UidRange.java index 26518d32ed..bc67c745c9 100644 --- a/framework/src/android/net/UidRange.java +++ b/framework/src/android/net/UidRange.java @@ -20,8 +20,11 @@ import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Range; import java.util.Collection; +import java.util.Set; /** * An inclusive range of UIDs. @@ -149,4 +152,32 @@ public final class UidRange implements Parcelable { } return false; } + + /** + * Convert a set of {@code Range} to a set of {@link UidRange}. + */ + @Nullable + public static ArraySet fromIntRanges(@Nullable Set> ranges) { + if (null == ranges) return null; + + final ArraySet uids = new ArraySet<>(); + for (Range range : ranges) { + uids.add(new UidRange(range.getLower(), range.getUpper())); + } + return uids; + } + + /** + * Convert a set of {@link UidRange} to a set of {@code Range}. + */ + @Nullable + public static ArraySet> toIntRanges(@Nullable Set ranges) { + if (null == ranges) return null; + + final ArraySet> uids = new ArraySet<>(); + for (UidRange range : ranges) { + uids.add(new Range(range.start, range.stop)); + } + return uids; + } } From 628cb1187ee994a2a7b974d286db6f796ab51405 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Fri, 19 Mar 2021 00:45:39 +0000 Subject: [PATCH 127/232] Revert "Revert "Expose uids related APIs in NetworkRequest and N..." Revert^2 "Add shims for NetworkRequest" b72b3ca768fc25ef72dc78f1396b08447b8ef5c6 NetworkRequest is moving into the incoming connectivity mainline module. The hidden setUids becomes inaccessible outside the module. Shims for support cts in different API levels will need to use it to verify the behavior of NetworkRequest. Thus, expose it to the API surface. Also, VPN uses getUids and setUids to control network capabilities. Networkcapabilities is a part of incoming connectivity mainline module but VPN is not. Thus, exposing these two methods are needed to allow VPN to continue using it. Test: make update-api Bug: 172183305 Change-Id: I4b8e1aa558e3459a932535f9901f4ae86b0ecb67 Merged-In: I107c329d4d7130d488772166eae8b5e7aaa2ff04 --- framework/api/module-lib-current.txt | 9 ++++++++ .../src/android/net/NetworkCapabilities.java | 21 +++++++++++++++++++ framework/src/android/net/NetworkRequest.java | 4 ++++ 3 files changed, 34 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index bb296476c7..6339094341 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -27,9 +27,18 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { + method @Nullable public java.util.Set> getUids(); field public static final int TRANSPORT_TEST = 7; // 0x7 } + public static final class NetworkCapabilities.Builder { + method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set>); + } + + public static class NetworkRequest.Builder { + method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set>); + } + public class ParseException extends java.lang.RuntimeException { ctor public ParseException(@NonNull String); ctor public ParseException(@NonNull String, @NonNull Throwable); diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index ba9f21b261..182bc7141a 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.ConnectivityManager.NetworkCallback; @@ -1477,8 +1478,13 @@ public final class NetworkCapabilities implements Parcelable { /** * Get the list of UIDs this network applies to. * This returns a copy of the set so that callers can't modify the original object. + * + * @return the list of UIDs this network applies to. If {@code null}, then the network applies + * to all UIDs. * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NullableCollection") public @Nullable Set> getUids() { return UidRange.toIntRanges(mUids); } @@ -2661,6 +2667,21 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Set the list of UIDs this network applies to. + * + * @param uids the list of UIDs this network applies to, or {@code null} if this network + * applies to all UIDs. + * @return this builder + * @hide + */ + @NonNull + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public Builder setUids(@Nullable Set> uids) { + mCaps.setUids(uids); + return this; + } + /** * Builds the instance of the capabilities. * diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 4ebbf06c51..cf131f0df6 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -36,6 +36,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.NetworkCapabilities.NetCapability; @@ -282,6 +283,9 @@ public class NetworkRequest implements Parcelable { * @return The builder to facilitate chaining. * @hide */ + @NonNull + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("MissingGetterMatchingBuilder") public Builder setUids(@Nullable Set> uids) { mNetworkCapabilities.setUids(uids); return this; From 1dddf2671e4860d6db74fb7fbde8a4c42afc837f Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Wed, 20 Jan 2021 20:47:32 +0900 Subject: [PATCH 128/232] [NS02] Mix in validation of the score Test: FrameworksNetTests FrameworksWifiTests NetworkStackTests Change-Id: I9cac3a05ad0c4008599973b12d2c5e4c02233a5c --- framework/src/android/net/NetworkScore.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java index f478010022..e640737168 100644 --- a/framework/src/android/net/NetworkScore.java +++ b/framework/src/android/net/NetworkScore.java @@ -33,13 +33,21 @@ public final class NetworkScore implements Parcelable { // a migration. private final int mLegacyInt; + // Agent-managed policies + // TODO : add them here, starting from 1 + + // Bitmask of all the policies applied to this score. + private final long mPolicies; + /** @hide */ - NetworkScore(final int legacyInt) { - this.mLegacyInt = legacyInt; + NetworkScore(final int legacyInt, final long policies) { + mLegacyInt = legacyInt; + mPolicies = policies; } private NetworkScore(@NonNull final Parcel in) { mLegacyInt = in.readInt(); + mPolicies = in.readLong(); } public int getLegacyInt() { @@ -54,6 +62,7 @@ public final class NetworkScore implements Parcelable { @Override public void writeToParcel(@NonNull final Parcel dest, final int flags) { dest.writeInt(mLegacyInt); + dest.writeLong(mPolicies); } @Override @@ -79,6 +88,7 @@ public final class NetworkScore implements Parcelable { * A builder for NetworkScore. */ public static final class Builder { + private static final long POLICY_NONE = 0L; private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE; private int mLegacyInt = INVALID_LEGACY_INT; @@ -102,7 +112,7 @@ public final class NetworkScore implements Parcelable { */ @NonNull public NetworkScore build() { - return new NetworkScore(mLegacyInt); + return new NetworkScore(mLegacyInt, POLICY_NONE); } } } From 816ea0324c6a7ad5dc0d5056933521a59edf3fd6 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Wed, 20 Jan 2021 20:47:32 +0900 Subject: [PATCH 129/232] [NS03] Mix in other CS-managed properties These properties are necessary to figure out the maximum score of a network. Test: FrameworksNetTests Change-Id: I48dce20ad4a80597039393dca607e8da829b2a61 --- framework/src/android/net/NetworkAgentConfig.java | 3 ++- framework/src/android/net/NetworkScore.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java index 664c2650ff..5e50a6404a 100644 --- a/framework/src/android/net/NetworkAgentConfig.java +++ b/framework/src/android/net/NetworkAgentConfig.java @@ -50,7 +50,8 @@ public final class NetworkAgentConfig implements Parcelable { * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to * connect to a particular access point is also explicit, though this may change in the future * as we want apps to use the multinetwork apis. - * + * TODO : this is a bad name, because it sounds like the user just tapped on the network. + * It's not necessarily the case ; auto-reconnection to WiFi has this true for example. * @hide */ public boolean explicitlySelected; diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java index e640737168..eadcb2d0a7 100644 --- a/framework/src/android/net/NetworkScore.java +++ b/framework/src/android/net/NetworkScore.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + /** * Object representing the quality of a network as perceived by the user. * @@ -35,6 +37,10 @@ public final class NetworkScore implements Parcelable { // Agent-managed policies // TODO : add them here, starting from 1 + /** @hide */ + public static final int MIN_AGENT_MANAGED_POLICY = 0; + /** @hide */ + public static final int MAX_AGENT_MANAGED_POLICY = -1; // Bitmask of all the policies applied to this score. private final long mPolicies; @@ -54,6 +60,14 @@ public final class NetworkScore implements Parcelable { return mLegacyInt; } + /** + * @return whether this score has a particular policy. + */ + @VisibleForTesting + public boolean hasPolicy(final int policy) { + return 0 != (mPolicies & (1L << policy)); + } + @Override public String toString() { return "Score(" + mLegacyInt + ")"; From 257d49c3000643852536f2fc3f529363cf93bef7 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 19 Mar 2021 14:42:50 +0000 Subject: [PATCH 130/232] Fix InetAddressCompat exception handling Fix InetAddressCompat exception handling to throw the original exception in case of InvocationTargetException, rather than wrapping in a generic IllegalStateException. Bug: 183198868 Test: Test device with and without connectivity Change-Id: Idc4d678afe9f20f920d7061790af4203ab75be26 --- .../src/android/net/InetAddressCompat.java | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/framework/src/android/net/InetAddressCompat.java b/framework/src/android/net/InetAddressCompat.java index 8404441de6..6b7e75c753 100644 --- a/framework/src/android/net/InetAddressCompat.java +++ b/framework/src/android/net/InetAddressCompat.java @@ -41,7 +41,12 @@ public class InetAddressCompat { public static void clearDnsCache() { try { InetAddress.class.getMethod("clearDnsCache").invoke(null); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + } catch (InvocationTargetException e) { + if (e.getCause() instanceof RuntimeException) { + throw (RuntimeException) e.getCause(); + } + throw new IllegalStateException("Unknown InvocationTargetException", e.getCause()); + } catch (IllegalAccessException | NoSuchMethodException e) { Log.wtf(InetAddressCompat.class.getSimpleName(), "Error clearing DNS cache", e); } } @@ -51,13 +56,7 @@ public class InetAddressCompat { */ public static InetAddress[] getAllByNameOnNet(String host, int netId) throws UnknownHostException { - try { - return (InetAddress[]) InetAddress.class.getMethod("getAllByNameOnNet", - String.class, int.class).invoke(null, host, netId); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling getAllByNameOnNet", e); - throw new IllegalStateException("Error querying via getAllNameOnNet", e); - } + return (InetAddress[]) callGetByNameMethod("getAllByNameOnNet", host, netId); } /** @@ -65,12 +64,25 @@ public class InetAddressCompat { */ public static InetAddress getByNameOnNet(String host, int netId) throws UnknownHostException { + return (InetAddress) callGetByNameMethod("getByNameOnNet", host, netId); + } + + private static Object callGetByNameMethod(String method, String host, int netId) + throws UnknownHostException { try { - return (InetAddress) InetAddress.class.getMethod("getByNameOnNet", - String.class, int.class).invoke(null, host, netId); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling getAllByNameOnNet", e); - throw new IllegalStateException("Error querying via getByNameOnNet", e); + return InetAddress.class.getMethod(method, String.class, int.class) + .invoke(null, host, netId); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof UnknownHostException) { + throw (UnknownHostException) e.getCause(); + } + if (e.getCause() instanceof RuntimeException) { + throw (RuntimeException) e.getCause(); + } + throw new IllegalStateException("Unknown InvocationTargetException", e.getCause()); + } catch (IllegalAccessException | NoSuchMethodException e) { + Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling " + method, e); + throw new IllegalStateException("Error querying via " + method, e); } } } From f353baf176b429e6f9a6f6314a34763b14af2146 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 19 Mar 2021 16:03:06 +0000 Subject: [PATCH 131/232] Add TEST_MAPPING for Connectivity The TEST_MAPPING triggers connectivity tests in other paths. Bug: 183198868 Change-Id: I8d9b0450ea4b3ef3e5115d1f858b1aa7badef742 Test: Needs treehugger for testing --- TEST_MAPPING | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 TEST_MAPPING diff --git a/TEST_MAPPING b/TEST_MAPPING new file mode 100644 index 0000000000..94f9232bc4 --- /dev/null +++ b/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "imports": [ + { + "path": "frameworks/base/core/java/android/net" + }, + { + "path": "packages/modules/NetworkStack" + }, + { + "path": "packages/modules/CaptivePortalLogin" + }, + { + "path": "packages/modules/Connectivity" + }, + { + "path": "packages/modules/Connectivity/Tethering" + } + ] +} \ No newline at end of file From e2d2838805f14b20b8f09e30e27863758e38b851 Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Tue, 23 Feb 2021 08:47:39 -0800 Subject: [PATCH 132/232] TransportInfo: Add a generic redaction mechanism This replaces the existing mechanism for redacting location sensitive fields with a more extensible mechanism. Currently supported redactions are for the following permissions: i. ACCESS_FINE_LOCATION ii. LOCAL_MAC_ADDRESS iii. NETWORK_SETTINGS Also, removed WifiInfo from ConnectivityServiceTest to reduce cross dependencies on wifi code. Bug: 156867433 Bug: 162602799 Test: atest android.net Test: atest com.android.server Change-Id: I2bb980c624667a55c1383f13ab71b9b97ed6eeab --- framework/api/module-lib-current.txt | 11 ++ framework/api/system-current.txt | 6 - framework/src/android/net/NetworkAgent.java | 5 +- .../src/android/net/NetworkCapabilities.java | 111 +++++++++++++++--- framework/src/android/net/TransportInfo.java | 48 +++++--- 5 files changed, 139 insertions(+), 42 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 6339094341..f484c3cdb0 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -27,7 +27,13 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { + ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, long); method @Nullable public java.util.Set> getUids(); + field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL + field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L + field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L + field public static final long REDACT_FOR_NETWORK_SETTINGS = 4L; // 0x4L + field public static final long REDACT_NONE = 0L; // 0x0L field public static final int TRANSPORT_TEST = 7; // 0x7 } @@ -79,6 +85,11 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator CREATOR; } + public interface TransportInfo { + method public default long getApplicableRedactions(); + method @NonNull public default android.net.TransportInfo makeCopy(long); + } + public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { ctor public VpnTransportInfo(int); method public int describeContents(); diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 4dca411cca..5cab0bcf1c 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -263,7 +263,6 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { - ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean); method @NonNull public int[] getAdministratorUids(); method @Nullable public String getSsid(); method @NonNull public int[] getTransportTypes(); @@ -437,11 +436,6 @@ package android.net { field public final int tcpWindowScale; } - public interface TransportInfo { - method public default boolean hasLocationSensitiveFields(); - method @NonNull public default android.net.TransportInfo makeCopy(boolean); - } - } package android.net.apf { diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index b3ab0ee8bd..c21aff9639 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -434,7 +434,7 @@ public abstract class NetworkAgent { } mInitialConfiguration = new InitialConfiguration(context, - new NetworkCapabilities(nc, /* parcelLocationSensitiveFields */ true), + new NetworkCapabilities(nc, NetworkCapabilities.REDACT_NONE), new LinkProperties(lp), score, config, ni); } @@ -878,8 +878,7 @@ public abstract class NetworkAgent { mBandwidthUpdatePending.set(false); mLastBwRefreshTime = System.currentTimeMillis(); final NetworkCapabilities nc = - new NetworkCapabilities(networkCapabilities, - /* parcelLocationSensitiveFields */ true); + new NetworkCapabilities(networkCapabilities, NetworkCapabilities.REDACT_NONE); queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc)); } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 182bc7141a..add9a15db4 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -19,6 +19,7 @@ package android.net; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import android.annotation.IntDef; +import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -65,6 +66,68 @@ import java.util.StringJoiner; public final class NetworkCapabilities implements Parcelable { private static final String TAG = "NetworkCapabilities"; + /** + * Mechanism to support redaction of fields in NetworkCapabilities that are guarded by specific + * app permissions. + **/ + /** + * Don't redact any fields since the receiving app holds all the necessary permissions. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final long REDACT_NONE = 0; + + /** + * Redact any fields that need {@link android.Manifest.permission#ACCESS_FINE_LOCATION} + * permission since the receiving app does not hold this permission or the location toggle + * is off. + * + * @see android.Manifest.permission#ACCESS_FINE_LOCATION + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1 << 0; + + /** + * Redact any fields that need {@link android.Manifest.permission#LOCAL_MAC_ADDRESS} + * permission since the receiving app does not hold this permission. + * + * @see android.Manifest.permission#LOCAL_MAC_ADDRESS + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 1 << 1; + + /** + * + * Redact any fields that need {@link android.Manifest.permission#NETWORK_SETTINGS} + * permission since the receiving app does not hold this permission. + * + * @see android.Manifest.permission#NETWORK_SETTINGS + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final long REDACT_FOR_NETWORK_SETTINGS = 1 << 2; + + /** + * Redact all fields in this object that require any relevant permission. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final long REDACT_ALL = -1L; + + /** @hide */ + @LongDef(flag = true, prefix = { "REDACT_" }, value = { + REDACT_NONE, + REDACT_FOR_ACCESS_FINE_LOCATION, + REDACT_FOR_LOCAL_MAC_ADDRESS, + REDACT_FOR_NETWORK_SETTINGS, + REDACT_ALL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RedactionType {} + // Set to true when private DNS is broken. private boolean mPrivateDnsBroken; @@ -79,32 +142,31 @@ public final class NetworkCapabilities implements Parcelable { private String mRequestorPackageName; /** - * Indicates whether parceling should preserve fields that are set based on permissions of - * the process receiving the {@link NetworkCapabilities}. + * Indicates what fields should be redacted from this instance. */ - private final boolean mParcelLocationSensitiveFields; + private final @RedactionType long mRedactions; public NetworkCapabilities() { - mParcelLocationSensitiveFields = false; + mRedactions = REDACT_ALL; clearAll(); mNetworkCapabilities = DEFAULT_CAPABILITIES; } public NetworkCapabilities(NetworkCapabilities nc) { - this(nc, false /* parcelLocationSensitiveFields */); + this(nc, REDACT_ALL); } /** * Make a copy of NetworkCapabilities. * * @param nc Original NetworkCapabilities - * @param parcelLocationSensitiveFields Whether to parcel location sensitive data or not. + * @param redactions bitmask of redactions that needs to be performed on this new instance of + * {@link NetworkCapabilities}. * @hide */ - @SystemApi - public NetworkCapabilities( - @Nullable NetworkCapabilities nc, boolean parcelLocationSensitiveFields) { - mParcelLocationSensitiveFields = parcelLocationSensitiveFields; + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public NetworkCapabilities(@Nullable NetworkCapabilities nc, @RedactionType long redactions) { + mRedactions = redactions; if (nc != null) { set(nc); } @@ -116,11 +178,13 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public void clearAll() { - // Ensures that the internal copies maintained by the connectivity stack does not set - // this bit. - if (mParcelLocationSensitiveFields) { + // Ensures that the internal copies maintained by the connectivity stack does not set it to + // anything other than |REDACT_ALL|. + if (mRedactions != REDACT_ALL) { + // This is needed because the current redaction mechanism relies on redaction while + // parceling. throw new UnsupportedOperationException( - "Cannot clear NetworkCapabilities when parcelLocationSensitiveFields is set"); + "Cannot clear NetworkCapabilities when mRedactions is set"); } mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0; mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; @@ -150,7 +214,7 @@ public final class NetworkCapabilities implements Parcelable { mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps; mNetworkSpecifier = nc.mNetworkSpecifier; if (nc.getTransportInfo() != null) { - setTransportInfo(nc.getTransportInfo().makeCopy(mParcelLocationSensitiveFields)); + setTransportInfo(nc.getTransportInfo().makeCopy(mRedactions)); } else { setTransportInfo(null); } @@ -2350,6 +2414,23 @@ public final class NetworkCapabilities implements Parcelable { } } + /** + * Returns a bitmask of all the applicable redactions (based on the permissions held by the + * receiving app) to be performed on this object. + * + * @return bitmask of redactions applicable on this instance. + * @hide + */ + public @RedactionType long getApplicableRedactions() { + // Currently, there are no fields redacted in NetworkCapabilities itself, so we just + // passthrough the redactions required by the embedded TransportInfo. If this changes + // in the future, modify this method. + if (mTransportInfo == null) { + return NetworkCapabilities.REDACT_NONE; + } + return mTransportInfo.getApplicableRedactions(); + } + /** * Builder class for NetworkCapabilities. * diff --git a/framework/src/android/net/TransportInfo.java b/framework/src/android/net/TransportInfo.java index aa4bbb0511..fa889eabb8 100644 --- a/framework/src/android/net/TransportInfo.java +++ b/framework/src/android/net/TransportInfo.java @@ -29,35 +29,47 @@ import android.annotation.SystemApi; public interface TransportInfo { /** - * Create a copy of a {@link TransportInfo} that will preserve location sensitive fields that - * were set based on the permissions of the process that originally received it. + * Create a copy of a {@link TransportInfo} with some fields redacted based on the permissions + * held by the receiving app. * - *

    By default {@link TransportInfo} does not preserve such fields during parceling, as - * they should not be shared outside of the process that receives them without appropriate - * checks. + *

    + * Usage by connectivity stack: + *

      + *
    • Connectivity stack will invoke {@link #getApplicableRedactions()} to find the list + * of redactions that are required by this {@link TransportInfo} instance.
    • + *
    • Connectivity stack then loops through each bit in the bitmask returned and checks if the + * receiving app holds the corresponding permission. + *
        + *
      • If the app holds the corresponding permission, the bit is cleared from the + * |redactions| bitmask.
      • + *
      • If the app does not hold the corresponding permission, the bit is retained in the + * |redactions| bitmask.
      • + *
      + *
    • Connectivity stack then invokes {@link #makeCopy(long)} with the necessary |redactions| + * to create a copy to send to the corresponding app.
    • + *
    + *

    * - * @param parcelLocationSensitiveFields Whether the location sensitive fields should be kept - * when parceling - * @return Copy of this instance. + * @param redactions bitmask of redactions that needs to be performed on this instance. + * @return Copy of this instance with the necessary redactions. * @hide */ - @SystemApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull - default TransportInfo makeCopy(boolean parcelLocationSensitiveFields) { + default TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) { return this; } /** - * Returns whether this TransportInfo type has location sensitive fields or not (helps - * to determine whether to perform a location permission check or not before sending to - * apps). + * Returns a bitmask of all the applicable redactions (based on the permissions held by the + * receiving app) to be performed on this TransportInfo. * - * @return {@code true} if this instance contains location sensitive info, {@code false} - * otherwise. + * @return bitmask of redactions applicable on this instance. + * @see #makeCopy(long) * @hide */ - @SystemApi - default boolean hasLocationSensitiveFields() { - return false; + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + default @NetworkCapabilities.RedactionType long getApplicableRedactions() { + return NetworkCapabilities.REDACT_NONE; } } From 3a5b9228901cb89394e91c7f3661bd697b372463 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 19 Mar 2021 17:04:08 +0900 Subject: [PATCH 133/232] Copy NetworkNotificationManager resources to conectivity module. The localized resources were automatically generated with: =========== export OLDRES=core/res/res export NEWRES=packages/Connectivity/service/ServiceConnectivityResources/res export FILE=services/core/java/com/android/server/connectivity/NetworkNotificationManager.java for i in $OLDRES/values-*/strings.xml; do outfile=$NEWRES/${i#core/res/res/} outdir=$(dirname $outfile) if egrep -q "$regex" $i || egrep -q "$strarrayregex" $i; then mkdir -p $outdir cat << EOF > $outfile EOF egrep $regex $i >> $outfile egrep -A $strarraylines "$strarrayregex" $i >> $outfile echo "" >> $outfile fi done =========== The text to the the base resources file was added manually from the output of: =========== egrep -B 2 $regex core/res/res/values/strings.xml | grep -v -- ^-- egrep -B 2 -A 6 $strarrayregex core/res/res/values/strings.xml | grep -v -- ^-- =========== The drawables were copied from the Wifi resources and from the framework resources. Test: m Bug: 183097033 Change-Id: I99c0d28069dd1a13d452105b0a83a03a833232a2 Merged-In: I99c0d28069dd1a13d452105b0a83a03a833232a2 --- .../stat_notify_rssi_in_range.png | Bin 0 -> 1160 bytes .../stat_notify_rssi_in_range.png | Bin 0 -> 855 bytes .../stat_notify_rssi_in_range.png | Bin 0 -> 1500 bytes .../stat_notify_rssi_in_range.png | Bin 0 -> 3517 bytes .../drawable/stat_notify_wifi_in_range.xml | 27 ++++++++ .../res/values-af/strings.xml | 39 ++++++++++++ .../res/values-am/strings.xml | 39 ++++++++++++ .../res/values-ar/strings.xml | 39 ++++++++++++ .../res/values-as/strings.xml | 39 ++++++++++++ .../res/values-az/strings.xml | 39 ++++++++++++ .../res/values-b+sr+Latn/strings.xml | 39 ++++++++++++ .../res/values-be/strings.xml | 39 ++++++++++++ .../res/values-bg/strings.xml | 39 ++++++++++++ .../res/values-bn/strings.xml | 39 ++++++++++++ .../res/values-bs/strings.xml | 39 ++++++++++++ .../res/values-ca/strings.xml | 39 ++++++++++++ .../res/values-cs/strings.xml | 39 ++++++++++++ .../res/values-da/strings.xml | 39 ++++++++++++ .../res/values-de/strings.xml | 39 ++++++++++++ .../res/values-el/strings.xml | 39 ++++++++++++ .../res/values-en-rAU/strings.xml | 39 ++++++++++++ .../res/values-en-rCA/strings.xml | 39 ++++++++++++ .../res/values-en-rGB/strings.xml | 39 ++++++++++++ .../res/values-en-rIN/strings.xml | 39 ++++++++++++ .../res/values-en-rXC/strings.xml | 39 ++++++++++++ .../res/values-es-rUS/strings.xml | 39 ++++++++++++ .../res/values-es/strings.xml | 39 ++++++++++++ .../res/values-et/strings.xml | 39 ++++++++++++ .../res/values-eu/strings.xml | 39 ++++++++++++ .../res/values-fa/strings.xml | 39 ++++++++++++ .../res/values-fi/strings.xml | 39 ++++++++++++ .../res/values-fr-rCA/strings.xml | 39 ++++++++++++ .../res/values-fr/strings.xml | 39 ++++++++++++ .../res/values-gl/strings.xml | 39 ++++++++++++ .../res/values-gu/strings.xml | 39 ++++++++++++ .../res/values-hi/strings.xml | 39 ++++++++++++ .../res/values-hr/strings.xml | 39 ++++++++++++ .../res/values-hu/strings.xml | 39 ++++++++++++ .../res/values-hy/strings.xml | 39 ++++++++++++ .../res/values-in/strings.xml | 39 ++++++++++++ .../res/values-is/strings.xml | 39 ++++++++++++ .../res/values-it/strings.xml | 39 ++++++++++++ .../res/values-iw/strings.xml | 39 ++++++++++++ .../res/values-ja/strings.xml | 39 ++++++++++++ .../res/values-ka/strings.xml | 39 ++++++++++++ .../res/values-kk/strings.xml | 39 ++++++++++++ .../res/values-km/strings.xml | 39 ++++++++++++ .../res/values-kn/strings.xml | 39 ++++++++++++ .../res/values-ko/strings.xml | 39 ++++++++++++ .../res/values-ky/strings.xml | 39 ++++++++++++ .../res/values-lo/strings.xml | 39 ++++++++++++ .../res/values-lt/strings.xml | 39 ++++++++++++ .../res/values-lv/strings.xml | 39 ++++++++++++ .../res/values-mk/strings.xml | 39 ++++++++++++ .../res/values-ml/strings.xml | 39 ++++++++++++ .../res/values-mn/strings.xml | 39 ++++++++++++ .../res/values-mr/strings.xml | 39 ++++++++++++ .../res/values-ms/strings.xml | 39 ++++++++++++ .../res/values-my/strings.xml | 39 ++++++++++++ .../res/values-nb/strings.xml | 39 ++++++++++++ .../res/values-ne/strings.xml | 39 ++++++++++++ .../res/values-nl/strings.xml | 39 ++++++++++++ .../res/values-or/strings.xml | 39 ++++++++++++ .../res/values-pa/strings.xml | 39 ++++++++++++ .../res/values-pl/strings.xml | 39 ++++++++++++ .../res/values-pt-rBR/strings.xml | 39 ++++++++++++ .../res/values-pt-rPT/strings.xml | 39 ++++++++++++ .../res/values-pt/strings.xml | 39 ++++++++++++ .../res/values-ro/strings.xml | 39 ++++++++++++ .../res/values-ru/strings.xml | 39 ++++++++++++ .../res/values-si/strings.xml | 39 ++++++++++++ .../res/values-sk/strings.xml | 39 ++++++++++++ .../res/values-sl/strings.xml | 39 ++++++++++++ .../res/values-sq/strings.xml | 39 ++++++++++++ .../res/values-sr/strings.xml | 39 ++++++++++++ .../res/values-sv/strings.xml | 39 ++++++++++++ .../res/values-sw/strings.xml | 39 ++++++++++++ .../res/values-ta/strings.xml | 39 ++++++++++++ .../res/values-te/strings.xml | 39 ++++++++++++ .../res/values-th/strings.xml | 39 ++++++++++++ .../res/values-tl/strings.xml | 39 ++++++++++++ .../res/values-tr/strings.xml | 39 ++++++++++++ .../res/values-uk/strings.xml | 39 ++++++++++++ .../res/values-ur/strings.xml | 39 ++++++++++++ .../res/values-uz/strings.xml | 39 ++++++++++++ .../res/values-vi/strings.xml | 39 ++++++++++++ .../res/values-zh-rCN/strings.xml | 39 ++++++++++++ .../res/values-zh-rHK/strings.xml | 39 ++++++++++++ .../res/values-zh-rTW/strings.xml | 39 ++++++++++++ .../res/values-zu/strings.xml | 39 ++++++++++++ .../res/values/strings.xml | 58 +++++++++++++++++- 91 files changed, 3398 insertions(+), 2 deletions(-) create mode 100644 service/ServiceConnectivityResources/res/drawable-hdpi/stat_notify_rssi_in_range.png create mode 100644 service/ServiceConnectivityResources/res/drawable-mdpi/stat_notify_rssi_in_range.png create mode 100644 service/ServiceConnectivityResources/res/drawable-xhdpi/stat_notify_rssi_in_range.png create mode 100644 service/ServiceConnectivityResources/res/drawable-xxhdpi/stat_notify_rssi_in_range.png create mode 100644 service/ServiceConnectivityResources/res/drawable/stat_notify_wifi_in_range.xml create mode 100644 service/ServiceConnectivityResources/res/values-af/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-am/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ar/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-as/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-az/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-be/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-bg/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-bn/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-bs/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ca/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-cs/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-da/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-de/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-el/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-en-rAU/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-en-rCA/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-en-rGB/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-en-rIN/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-en-rXC/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-es-rUS/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-es/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-et/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-eu/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-fa/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-fi/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-fr/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-gl/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-gu/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-hi/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-hr/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-hu/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-hy/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-in/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-is/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-it/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-iw/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ja/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ka/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-kk/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-km/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-kn/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ko/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ky/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-lo/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-lt/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-lv/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-mk/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ml/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-mn/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-mr/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ms/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-my/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-nb/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ne/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-nl/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-or/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-pa/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-pl/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-pt/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ro/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ru/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-si/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-sk/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-sl/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-sq/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-sr/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-sv/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-sw/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ta/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-te/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-th/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-tl/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-tr/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-uk/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-ur/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-uz/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-vi/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml create mode 100644 service/ServiceConnectivityResources/res/values-zu/strings.xml diff --git a/service/ServiceConnectivityResources/res/drawable-hdpi/stat_notify_rssi_in_range.png b/service/ServiceConnectivityResources/res/drawable-hdpi/stat_notify_rssi_in_range.png new file mode 100644 index 0000000000000000000000000000000000000000..74977e6aefffb385b33ee169faf7bd0c63cf38a2 GIT binary patch literal 1160 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBGB7kWGcf%852S(Or3MTPuM!v-tbhiH=TC}1>IPKHmgMd3 z!tfsi7wla=87RV8;1OBOz`!j8!i<;h*8O2%V6OFaaSVw#e0Ik9{2oV%qAN=gZp5G0DtB#^cY51)M^&JnqQOvG7o__`xl6_Yb>;XTg z`)zT?c=zR-mG^76?-rfBXXd#x#^*oR&iiTi*+=}}=Cq{AN{{%t9oqNvglyUFsi>5k z6fN|{Sg9!VY0o#_gDmmWJHGs|N}4_A)25C+fsaMsrn{!t&Dg!@lWb%Ilft=&f`XPW z&s)msqlBnYH1dbce&)yF-SSn(WcV9B6>;Ea{C5bJ2!WicTonE!wYJY%hfcBnu zEBcszH_qpsbB_On@)z$15iU!-W-y##e4}eK`|`t@hAGZWUzpT4)Oa#`+oGd5@)_`r11Qy zh1uo{9kskE*O(SZJX#+1cN(K)@|^a`2Xa^J{vq_{aJO@4M3^ z_4bI^>9_~m*_SPyCc03|OlM~81l9*K8+d0N6g#_ZOWP8^%8#;)-;%V;qm>>@pWr@r zevVPDLP2mt&;gzV?pJq;8Zspm{oOXOmR0Ojn#`%8kpDxhOz^jPgHl3OBli`rlbg0# zIm~+Z?vZw~{ytHix*6W5+$Vo7y}484ZF9Pdd4Xg^|K1 zRdP`(kYX@0Ff`ING|)9L3NbXXGBL6OvCXXv3~mHe^P_0U%}>cptHiBgCGX+QKn)C@ Lu6{1-oD!M!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pWm85kOx85n;42hu?CQUeBtR|yOZRx=nF#PcV`A9Vw&WlQpQ zcVYMsf(!O8p9~b?EbxddW?97dyXIW z_-xmgRDMHe%2KTZ-;=rSd!OICB3)Q>|Dn|k67KTshs$=IC{x^Y;(>J`+vmG%FH1XC zbr*dt)aCf!Xf69`RnryEH3!f0UF?=jxm2`L>dv9unnSF6bd=7i&g!{T%Uyf2;fnJY zw(0`oZ+ed-S%RC^A63oajoTH$tdf56<@vT|yLtSI_NR!0*93uY=PqB#Kh5A9S2|DPEiP-8Gx^4KK94E**E9bFJ*+g}C^Q z&3?%}g`47*eukY-MMjL1Spl=*TicDN5G!Eb+n^ zTVXQlNiQBo#AKTnQN7ZfQ!itQH~xHoymQZYzx&;DzkC0?`Q8K{g003DxSDcTS zO%H&O5NAKgR<4*}Q5a7Q0CW^f?}ov}8YKI;V1c&rBOk;1-?vmP(HxSfYoDJio<>%rTeSDD?XnFuQ9pha4Y&55Jc=qk zOWLO)LM2AkuaDqYWl{VpwR7ub*mgAJ3>VOvhYNpVLF{3bkiE%uIaP21lwVoLJejvH z`14mu`Ut(duZUAIq&F#9Jr%{II!M|>ciT6Y!9j=?Xl=-F6dDSBgZ`FuHesCRn#}|p z<5a9S>}_5%Rt09kFGeDr_dBve3)0V&wBp9Wn3yYSB515(-ODsS%wry)xDA!HEMQ|i z-mM+=MRs>w)Musdr3rMGN}s*(PuD~gjCcpRD<2ZJinbMAqA@kU=u^N%NC-D~rKHft zl6TZt`2_E2haO4o5nL-4&O&A`d<*~TEijU_uhV_~!hda=s;$j#bD*MP>zWuA?zr>I zxuQQbE(rC7WgTf}bE3*38y*{0Cxll$_lo6}e`h2y{2pMjjqlA%W>-!--zNR`hNtfp zq+v+6X7pyOY%|+q3#EB%&ZDQ@BJ3X9o^1&h`#E_sGQx&9}e!aV#8Q6 z_IVSDRrTfk32e9&lK&z2psrHPw- zJWG|5tUP*umC9APEozA?O(-Nv^I>BhyBv{m95w3A)~cf6%y7-hqUQFtSCEw@T}5Ce z2)Ozs`hqY4aW{CXOi#_W+1L(IJ&{A_e4_0pr{jDdEIp5^Np<2?&sA$Z%@7c6>qi5m z{L^d@l{fZwbahJ}u)|I+-Dc%8x^Es6bw$nkW)BDI$ZkO7TCCr8PdB>G+pyFJ(TE0O zgzAurHC?M0lBo_+mLLk&=?$kqn$Kwa7kJm8FCX0tp#(uBHU@h79RCVQyJtKfZwD#` zP3D+42FlHcq~#PYPA?$*-KW1!{NtI{W!b~|lrNy2X_a3rcCZpY%iw-zI|L@WuMxD_ z1A+;6FCUH8F?gl{Bp(YWS+KOUHp$TC3a4Zl@IN*23t9KjdWDDQtIn)t1H7Wmkfp+$MX3PYFG|HbpTp z@z6|k3(s8Kf0YuY#402k2<>S`2~oAL9};h>26?C3*sC&Y?x`~?9J@!py+Wvpeu(5X zAi?#>Ce4}LBGe43-z~pg_Iv6j`mz^MjvJsAL~M<%>CWS_#-(j)0yYKZNT~k$*qqH3 z3~rnIMD0g;6}cQ2S^V)9t|qIJ3`OyC+tJOW0YT-lbLeOtQ{QC5v{k3qaZV#R#B(*7 z)sL7AZYHM&mueXJfkG~OLOG%^K>YFaQtKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0008$NklGu?NrI1pWfoHauto{KyCg zHU0=V^kC)eC`iVa*a#RFyune)khX!YjTjUee6SJFD*;XfY}yLaNuDzW_JF?noF~|1zLH(APb-iPXu0Cy9<0%ki4(OdXBxT348$_ zhElA?$ph0~d83jahf>WS#*`#e3n~6dT9xbrpq|niIvcM-yCV+HQOx!4C1$tM0S%e= zf!Z-k%cS>oLjp8OC@rR{EHSB)KZF8kl8~{+hT#-a$)<&3nrtj)r? zZ31rvklu3!vc#6#dMGNn3_y!JE+j}VQOW!2Q?l4CyMx0W7oh=a0G~rt$*uqms-Sml z0qRr2j*CzLZ2@nj)X=Nsh!wCrFAiJ4Q_^in__c6;o^sCR!uijXbN(!x|0exriR+{R rHTi?3a;td(B!C2vm;e$$_UzvPcEfJQwa{BG00000NkvXXu0mjfV3c|Q literal 0 HcmV?d00001 diff --git a/service/ServiceConnectivityResources/res/drawable/stat_notify_wifi_in_range.xml b/service/ServiceConnectivityResources/res/drawable/stat_notify_wifi_in_range.xml new file mode 100644 index 0000000000..a271ca5224 --- /dev/null +++ b/service/ServiceConnectivityResources/res/drawable/stat_notify_wifi_in_range.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/service/ServiceConnectivityResources/res/values-af/strings.xml b/service/ServiceConnectivityResources/res/values-af/strings.xml new file mode 100644 index 0000000000..68720d5d27 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-af/strings.xml @@ -0,0 +1,39 @@ + + + + "Meld aan by Wi-Fi-netwerk" + "Meld by netwerk aan" + + "%1$s het geen internettoegang nie" + "Tik vir opsies" + "Selnetwerk het nie internettoegang nie" + "Netwerk het nie internettoegang nie" + "Daar kan nie by private DNS-bediener ingegaan word nie" + "%1$s het beperkte konnektiwiteit" + "Tik om in elk geval te koppel" + "Het oorgeskakel na %1$s" + "Toestel gebruik %1$s wanneer %2$s geen internettoegang het nie. Heffings kan geld." + "Het oorgeskakel van %1$s na %2$s" + "\'n onbekende netwerktipe" + + "mobiele data" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-am/strings.xml b/service/ServiceConnectivityResources/res/values-am/strings.xml new file mode 100644 index 0000000000..78d928351d --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-am/strings.xml @@ -0,0 +1,39 @@ + + + + "ወደ Wi-Fi አውታረ መረብ በመለያ ግባ" + "ወደ አውታረ መረብ በመለያ ይግቡ" + + "%1$s ምንም የበይነ መረብ መዳረሻ የለም" + "ለአማራጮች መታ ያድርጉ" + "የተንቀሳቃሽ ስልክ አውታረ መረብ የበይነመረብ መዳረሻ የለውም" + "አውታረ መረብ የበይነመረብ መዳረሻ የለውም" + "የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም" + "%1$s የተገደበ ግንኙነት አለው" + "ለማንኛውም ለማገናኘት መታ ያድርጉ" + "ወደ %1$s ተቀይሯል" + "%2$s ምንም ዓይነት የበይነመረብ ግንኙነት በማይኖረው ጊዜ መሣሪያዎች %1$sን ይጠቀማሉ። ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ።" + "ከ%1$s ወደ %2$s ተቀይሯል" + "አንድ ያልታወቀ አውታረ መረብ ዓይነት" + + "የተንቀሳቃሽ ስልክ ውሂብ" + "Wi-Fi" + "ብሉቱዝ" + "ኤተርኔት" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ar/strings.xml b/service/ServiceConnectivityResources/res/values-ar/strings.xml new file mode 100644 index 0000000000..8698a7e6e1 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ar/strings.xml @@ -0,0 +1,39 @@ + + + + "‏تسجيل الدخول إلى شبكة Wi-Fi" + "تسجيل الدخول إلى الشبكة" + + "لا يتوفّر في %1$s إمكانية الاتصال بالإنترنت." + "انقر للحصول على الخيارات." + "شبكة الجوّال هذه غير متصلة بالإنترنت" + "الشبكة غير متصلة بالإنترنت" + "لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص" + "إمكانية اتصال %1$s محدودة." + "يمكنك النقر للاتصال على أي حال." + "تم التبديل إلى %1$s" + "يستخدم الجهاز %1$s عندما لا يتوفر اتصال بالإنترنت في شبكة %2$s، ويمكن أن يتم فرض رسوم مقابل ذلك." + "تم التبديل من %1$s إلى %2$s" + "نوع شبكة غير معروف" + + "بيانات الجوّال" + "Wi-Fi" + "بلوتوث" + "إيثرنت" + "‏شبكة افتراضية خاصة (VPN)" + + diff --git a/service/ServiceConnectivityResources/res/values-as/strings.xml b/service/ServiceConnectivityResources/res/values-as/strings.xml new file mode 100644 index 0000000000..10b234adce --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-as/strings.xml @@ -0,0 +1,39 @@ + + + + "ৱাই-ফাই নেটৱৰ্কত ছাইন ইন কৰক" + "নেটৱৰ্কত ছাইন ইন কৰক" + + "%1$sৰ ইণ্টাৰনেটৰ এক্সেছ নাই" + "অধিক বিকল্পৰ বাবে টিপক" + "ম’বাইল নেটৱৰ্কৰ কোনো ইণ্টাৰনেটৰ এক্সেছ নাই" + "নেটৱৰ্কৰ কোনো ইণ্টাৰনেটৰ এক্সেছ নাই" + "ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি" + "%1$sৰ সকলো সেৱাৰ এক্সেছ নাই" + "যিকোনো প্ৰকাৰে সংযোগ কৰিবলৈ টিপক" + "%1$sলৈ সলনি কৰা হ’ল" + "যেতিয়া %2$sত ইণ্টাৰনেট নাথাকে, তেতিয়া ডিভাইচে %1$sক ব্যৱহাৰ কৰে। মাচুল প্ৰযোজ্য হ\'ব পাৰে।" + "%1$sৰ পৰা %2$s লৈ সলনি কৰা হ’ল" + "অজ্ঞাত প্ৰকাৰৰ নেটৱৰ্ক" + + "ম’বাইল ডেটা" + "ৱাই-ফাই" + "ব্লুটুথ" + "ইথাৰনেট" + "ভিপিএন" + + diff --git a/service/ServiceConnectivityResources/res/values-az/strings.xml b/service/ServiceConnectivityResources/res/values-az/strings.xml new file mode 100644 index 0000000000..d75a20460c --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-az/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi şəbəkəsinə daxil ol" + "Şəbəkəyə daxil olun" + + "%1$s üçün internet girişi əlçatan deyil" + "Seçimlər üçün tıklayın" + "Mobil şəbəkənin internetə girişi yoxdur" + "Şəbəkənin internetə girişi yoxdur" + "Özəl DNS serverinə giriş mümkün deyil" + "%1$s bağlantını məhdudlaşdırdı" + "İstənilən halda klikləyin" + "%1$s şəbəkə növünə keçirildi" + "%2$s şəbəkəsinin internetə girişi olmadıqda, cihaz %1$s şəbəkəsini istifadə edir. Xidmət haqqı tutula bilər." + "%1$s şəbəkəsindən %2$s şəbəkəsinə keçirildi" + "naməlum şəbəkə növü" + + "mobil data" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml b/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml new file mode 100644 index 0000000000..11e4957a7b --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-b+sr+Latn/strings.xml @@ -0,0 +1,39 @@ + + + + "Prijavljivanje na WiFi mrežu" + "Prijavite se na mrežu" + + "%1$s nema pristup internetu" + "Dodirnite za opcije" + "Mobilna mreža nema pristup internetu" + "Mreža nema pristup internetu" + "Pristup privatnom DNS serveru nije uspeo" + "%1$s ima ograničenu vezu" + "Dodirnite da biste se ipak povezali" + "Prešli ste na tip mreže %1$s" + "Uređaj koristi tip mreže %1$s kada tip mreže %2$s nema pristup internetu. Možda će se naplaćivati troškovi." + "Prešli ste sa tipa mreže %1$s na tip mreže %2$s" + "nepoznat tip mreže" + + "mobilni podaci" + "WiFi" + "Bluetooth" + "Eternet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-be/strings.xml b/service/ServiceConnectivityResources/res/values-be/strings.xml new file mode 100644 index 0000000000..6b0b1f1130 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-be/strings.xml @@ -0,0 +1,39 @@ + + + + "Уваход у сетку Wi-Fi" + "Увайдзіце ў сетку" + + "%1$s не мае доступу ў інтэрнэт" + "Дакраніцеся, каб убачыць параметры" + "Мабільная сетка не мае доступу ў інтэрнэт" + "Сетка не мае доступу ў інтэрнэт" + "Не ўдалося атрымаць доступ да прыватнага DNS-сервера" + "%1$s мае абмежаваную магчымасць падключэння" + "Націсніце, каб падключыцца" + "Выкананы пераход да %1$s" + "Прылада выкарыстоўвае сетку %1$s, калі ў сетцы %2$s няма доступу да інтэрнэту. Можа спаганяцца плата." + "Выкананы пераход з %1$s да %2$s" + "невядомы тып сеткі" + + "мабільная перадача даных" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-bg/strings.xml b/service/ServiceConnectivityResources/res/values-bg/strings.xml new file mode 100644 index 0000000000..6427bd09cf --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-bg/strings.xml @@ -0,0 +1,39 @@ + + + + "Влизане в Wi-Fi мрежа" + "Вход в мрежата" + + "%1$s няма достъп до интернет" + "Докоснете за опции" + "Мобилната мрежа няма достъп до интернет" + "Мрежата няма достъп до интернет" + "Не може да се осъществи достъп до частния DNS сървър" + "%1$s има ограничена свързаност" + "Докоснете, за да се свържете въпреки това" + "Превключи се към %1$s" + "Устройството използва %1$s, когато %2$s няма достъп до интернет. Възможно е да бъдете таксувани." + "Превключи се от %1$s към %2$s" + "неизвестен тип мрежа" + + "мобилни данни" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "виртуална частна мрежа (VPN)" + + diff --git a/service/ServiceConnectivityResources/res/values-bn/strings.xml b/service/ServiceConnectivityResources/res/values-bn/strings.xml new file mode 100644 index 0000000000..74d4cd6d7e --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-bn/strings.xml @@ -0,0 +1,39 @@ + + + + "ওয়াই-ফাই নেটওয়ার্কে সাইন-ইন করুন" + "নেটওয়ার্কে সাইন-ইন করুন" + + "%1$s-এর ইন্টারনেটে অ্যাক্সেস নেই" + "বিকল্পগুলির জন্য আলতো চাপুন" + "মোবাইল নেটওয়ার্কে কোনও ইন্টারনেট অ্যাক্সেস নেই" + "নেটওয়ার্কে কোনও ইন্টারনেট অ্যাক্সেস নেই" + "ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না" + "%1$s-এর সীমিত কানেক্টিভিটি আছে" + "তবুও কানেক্ট করতে ট্যাপ করুন" + "%1$s এ পাল্টানো হয়েছে" + "%2$s এ ইন্টারনেট অ্যাক্সেস না থাকলে %1$s ব্যবহার করা হয়৷ ডেটা চার্জ প্রযোজ্য৷" + "%1$s থেকে %2$s এ পাল্টানো হয়েছে" + "এই নেটওয়ার্কের প্রকার অজানা" + + "মোবাইল ডেটা" + "Wi-Fi" + "ব্লুটুথ" + "ইথারনেট" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-bs/strings.xml b/service/ServiceConnectivityResources/res/values-bs/strings.xml new file mode 100644 index 0000000000..19f6e1a3f5 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-bs/strings.xml @@ -0,0 +1,39 @@ + + + + "Prijavljivanje na WiFi mrežu" + "Prijava na mrežu" + + "Mreža %1$s nema pristup internetu" + "Dodirnite za opcije" + "Mobilna mreža nema pristup internetu" + "Mreža nema pristup internetu" + "Nije moguće pristupiti privatnom DNS serveru" + "Mreža %1$s ima ograničenu povezivost" + "Dodirnite da se ipak povežete" + "Prebačeno na: %1$s" + "Kada %2$s nema pristup internetu, uređaj koristi mrežu %1$s. Moguća je naplata usluge." + "Prebačeno iz mreže %1$s u %2$s mrežu" + "nepoznata vrsta mreže" + + "prijenos podataka na mobilnoj mreži" + "WiFi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ca/strings.xml b/service/ServiceConnectivityResources/res/values-ca/strings.xml new file mode 100644 index 0000000000..c55684d6e0 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ca/strings.xml @@ -0,0 +1,39 @@ + + + + "Inicia la sessió a la xarxa Wi-Fi" + "Inicia la sessió a la xarxa" + + "%1$s no té accés a Internet" + "Toca per veure les opcions" + "La xarxa mòbil no té accés a Internet" + "La xarxa no té accés a Internet" + "No es pot accedir al servidor DNS privat" + "%1$s té una connectivitat limitada" + "Toca per connectar igualment" + "Actualment en ús: %1$s" + "El dispositiu utilitza %1$s en cas que %2$s no tingui accés a Internet. És possible que s\'hi apliquin càrrecs." + "Abans es feia servir la xarxa %1$s; ara s\'utilitza %2$s" + "una tipus de xarxa desconegut" + + "dades mòbils" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-cs/strings.xml b/service/ServiceConnectivityResources/res/values-cs/strings.xml new file mode 100644 index 0000000000..fa8c411e48 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-cs/strings.xml @@ -0,0 +1,39 @@ + + + + "Přihlásit se k síti Wi-Fi" + "Přihlásit se k síti" + + "Síť %1$s nemá přístup k internetu" + "Klepnutím zobrazíte možnosti" + "Mobilní síť nemá přístup k internetu" + "Síť nemá přístup k internetu" + "Nelze získat přístup k soukromému serveru DNS" + "Síť %1$s umožňuje jen omezené připojení" + "Klepnutím se i přesto připojíte" + "Přechod na síť %1$s" + "Když síť %2$s nebude mít přístup k internetu, zařízení použije síť %1$s. Mohou být účtovány poplatky." + "Přechod ze sítě %1$s na síť %2$s" + "neznámý typ sítě" + + "mobilní data" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-da/strings.xml b/service/ServiceConnectivityResources/res/values-da/strings.xml new file mode 100644 index 0000000000..f7be6df981 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-da/strings.xml @@ -0,0 +1,39 @@ + + + + "Log ind på Wi-Fi-netværk" + "Log ind på netværk" + + "%1$s har ingen internetforbindelse" + "Tryk for at se valgmuligheder" + "Mobilnetværket har ingen internetadgang" + "Netværket har ingen internetadgang" + "Der er ikke adgang til den private DNS-server" + "%1$s har begrænset forbindelse" + "Tryk for at oprette forbindelse alligevel" + "Der blev skiftet til %1$s" + "Enheden benytter %1$s, når der ikke er internetadgang via %2$s. Der opkræves muligvis betaling." + "Der blev skiftet fra %1$s til %2$s" + "en ukendt netværkstype" + + "mobildata" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-de/strings.xml b/service/ServiceConnectivityResources/res/values-de/strings.xml new file mode 100644 index 0000000000..1e7b80c8f6 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-de/strings.xml @@ -0,0 +1,39 @@ + + + + "In WLAN anmelden" + "Im Netzwerk anmelden" + + "%1$s hat keinen Internetzugriff" + "Für Optionen tippen" + "Mobiles Netzwerk hat keinen Internetzugriff" + "Netzwerk hat keinen Internetzugriff" + "Auf den privaten DNS-Server kann nicht zugegriffen werden" + "Schlechte Verbindung mit %1$s" + "Tippen, um die Verbindung trotzdem herzustellen" + "Zu %1$s gewechselt" + "Auf dem Gerät werden %1$s genutzt, wenn über %2$s kein Internet verfügbar ist. Eventuell fallen Gebühren an." + "Von \"%1$s\" zu \"%2$s\" gewechselt" + "ein unbekannter Netzwerktyp" + + "Mobile Daten" + "WLAN" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-el/strings.xml b/service/ServiceConnectivityResources/res/values-el/strings.xml new file mode 100644 index 0000000000..89647fdb1e --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-el/strings.xml @@ -0,0 +1,39 @@ + + + + "Συνδεθείτε στο δίκτυο Wi-Fi" + "Σύνδεση στο δίκτυο" + + "Η εφαρμογή %1$s δεν έχει πρόσβαση στο διαδίκτυο" + "Πατήστε για να δείτε τις επιλογές" + "Το δίκτυο κινητής τηλεφωνίας δεν έχει πρόσβαση στο διαδίκτυο." + "Το δίκτυο δεν έχει πρόσβαση στο διαδίκτυο." + "Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS." + "Το δίκτυο %1$s έχει περιορισμένη συνδεσιμότητα" + "Πατήστε για σύνδεση ούτως ή άλλως" + "Μετάβαση σε δίκτυο %1$s" + "Η συσκευή χρησιμοποιεί το δίκτυο %1$s όταν το δίκτυο %2$s δεν έχει πρόσβαση στο διαδίκτυο. Μπορεί να ισχύουν χρεώσεις." + "Μετάβαση από το δίκτυο %1$s στο δίκτυο %2$s" + "άγνωστος τύπος δικτύου" + + "δεδομένα κινητής τηλεφωνίας" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml b/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml new file mode 100644 index 0000000000..d29e2ec9f3 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-en-rAU/strings.xml @@ -0,0 +1,39 @@ + + + + "Sign in to a Wi-Fi network" + "Sign in to network" + + "%1$s has no Internet access" + "Tap for options" + "Mobile network has no Internet access" + "Network has no Internet access" + "Private DNS server cannot be accessed" + "%1$s has limited connectivity" + "Tap to connect anyway" + "Switched to %1$s" + "Device uses %1$s when %2$s has no Internet access. Charges may apply." + "Switched from %1$s to %2$s" + "an unknown network type" + + "mobile data" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml b/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml new file mode 100644 index 0000000000..d29e2ec9f3 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-en-rCA/strings.xml @@ -0,0 +1,39 @@ + + + + "Sign in to a Wi-Fi network" + "Sign in to network" + + "%1$s has no Internet access" + "Tap for options" + "Mobile network has no Internet access" + "Network has no Internet access" + "Private DNS server cannot be accessed" + "%1$s has limited connectivity" + "Tap to connect anyway" + "Switched to %1$s" + "Device uses %1$s when %2$s has no Internet access. Charges may apply." + "Switched from %1$s to %2$s" + "an unknown network type" + + "mobile data" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml b/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml new file mode 100644 index 0000000000..d29e2ec9f3 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-en-rGB/strings.xml @@ -0,0 +1,39 @@ + + + + "Sign in to a Wi-Fi network" + "Sign in to network" + + "%1$s has no Internet access" + "Tap for options" + "Mobile network has no Internet access" + "Network has no Internet access" + "Private DNS server cannot be accessed" + "%1$s has limited connectivity" + "Tap to connect anyway" + "Switched to %1$s" + "Device uses %1$s when %2$s has no Internet access. Charges may apply." + "Switched from %1$s to %2$s" + "an unknown network type" + + "mobile data" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml b/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml new file mode 100644 index 0000000000..d29e2ec9f3 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-en-rIN/strings.xml @@ -0,0 +1,39 @@ + + + + "Sign in to a Wi-Fi network" + "Sign in to network" + + "%1$s has no Internet access" + "Tap for options" + "Mobile network has no Internet access" + "Network has no Internet access" + "Private DNS server cannot be accessed" + "%1$s has limited connectivity" + "Tap to connect anyway" + "Switched to %1$s" + "Device uses %1$s when %2$s has no Internet access. Charges may apply." + "Switched from %1$s to %2$s" + "an unknown network type" + + "mobile data" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml b/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml new file mode 100644 index 0000000000..cd69133966 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-en-rXC/strings.xml @@ -0,0 +1,39 @@ + + + + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎Sign in to Wi-Fi network‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‎Sign in to network‎‏‎‎‏‎" + + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ has no internet access‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎Tap for options‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‎‏‎‎‎‎‏‎‏‎‏‎Mobile network has no internet access‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎Network has no internet access‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‏‏‎Private DNS server cannot be accessed‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ has limited connectivity‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎Tap to connect anyway‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‎Switched to ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎Device uses ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ when ‎‏‎‎‏‏‎%2$s‎‏‎‎‏‏‏‎ has no internet access. Charges may apply.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‏‏‏‎Switched from ‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ to ‎‏‎‎‏‏‎%2$s‎‏‎‎‏‏‏‎‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎an unknown network type‎‏‎‎‏‎" + + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‎mobile data‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‎‎‏‎‎‎‎Wi-Fi‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‎‎Bluetooth‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‎‎‎Ethernet‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‏‏‎VPN‎‏‎‎‏‎" + + diff --git a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml new file mode 100644 index 0000000000..9102dc019a --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml @@ -0,0 +1,39 @@ + + + + "Accede a una red Wi-Fi." + "Acceder a la red" + + "%1$sno tiene acceso a Internet" + "Presiona para ver opciones" + "La red móvil no tiene acceso a Internet" + "La red no tiene acceso a Internet" + "No se puede acceder al servidor DNS privado" + "%1$s tiene conectividad limitada" + "Presiona para conectarte de todas formas" + "Se cambió a %1$s" + "El dispositivo usa %1$s cuando %2$s no tiene acceso a Internet. Es posible que se apliquen cargos." + "Se cambió de %1$s a %2$s" + "un tipo de red desconocido" + + "Datos móviles" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-es/strings.xml b/service/ServiceConnectivityResources/res/values-es/strings.xml new file mode 100644 index 0000000000..4c15566a73 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-es/strings.xml @@ -0,0 +1,39 @@ + + + + "Iniciar sesión en red Wi-Fi" + "Iniciar sesión en la red" + + "%1$s no tiene acceso a Internet" + "Toca para ver opciones" + "La red móvil no tiene acceso a Internet" + "La red no tiene acceso a Internet" + "No se ha podido acceder al servidor DNS privado" + "%1$s tiene una conectividad limitada" + "Toca para conectarte de todas formas" + "Se ha cambiado a %1$s" + "El dispositivo utiliza %1$s cuando %2$s no tiene acceso a Internet. Es posible que se apliquen cargos." + "Se ha cambiado de %1$s a %2$s" + "tipo de red desconocido" + + "datos móviles" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-et/strings.xml b/service/ServiceConnectivityResources/res/values-et/strings.xml new file mode 100644 index 0000000000..398223a341 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-et/strings.xml @@ -0,0 +1,39 @@ + + + + "Logi sisse WiFi-võrku" + "Võrku sisselogimine" + + "Võrgul %1$s puudub Interneti-ühendus" + "Puudutage valikute nägemiseks" + "Mobiilsidevõrgul puudub Interneti-ühendus" + "Võrgul puudub Interneti-ühendus" + "Privaatsele DNS-serverile ei pääse juurde" + "Võrgu %1$s ühendus on piiratud" + "Puudutage, kui soovite siiski ühenduse luua" + "Lülitati võrgule %1$s" + "Seade kasutab võrku %1$s, kui võrgul %2$s puudub juurdepääs Internetile. Rakenduda võivad tasud." + "Lülitati võrgult %1$s võrgule %2$s" + "tundmatu võrgutüüp" + + "mobiilne andmeside" + "WiFi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-eu/strings.xml b/service/ServiceConnectivityResources/res/values-eu/strings.xml new file mode 100644 index 0000000000..dd70316f69 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-eu/strings.xml @@ -0,0 +1,39 @@ + + + + "Hasi saioa Wi-Fi sarean" + "Hasi saioa sarean" + + "Ezin da konektatu Internetera %1$s sarearen bidez" + "Sakatu aukerak ikusteko" + "Sare mugikorra ezin da konektatu Internetera" + "Sarea ezin da konektatu Internetera" + "Ezin da atzitu DNS zerbitzari pribatua" + "%1$s sareak konektagarritasun murriztua du" + "Sakatu hala ere konektatzeko" + "%1$s erabiltzen ari zara orain" + "%2$s Internetera konektatzeko gauza ez denean, %1$s erabiltzen du gailuak. Agian kostuak ordaindu beharko dituzu." + "%1$s erabiltzen ari zinen, baina %2$s erabiltzen ari zara orain" + "sare mota ezezaguna" + + "datu-konexioa" + "Wifia" + "Bluetooth-a" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-fa/strings.xml b/service/ServiceConnectivityResources/res/values-fa/strings.xml new file mode 100644 index 0000000000..46a946c50b --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-fa/strings.xml @@ -0,0 +1,39 @@ + + + + "‏ورود به شبکه Wi-Fi" + "ورود به سیستم شبکه" + + "%1$s به اینترنت دسترسی ندارد" + "برای گزینه‌ها ضربه بزنید" + "شبکه تلفن همراه به اینترنت دسترسی ندارد" + "شبکه به اینترنت دسترسی ندارد" + "‏سرور DNS خصوصی قابل دسترسی نیست" + "%1$s اتصال محدودی دارد" + "به‌هرصورت، برای اتصال ضربه بزنید" + "به %1$s تغییر کرد" + "وقتی %2$s به اینترنت دسترسی نداشته باشد، دستگاه از %1$s استفاده می‌کند. ممکن است هزینه‌هایی اعمال شود." + "از %1$s به %2$s تغییر کرد" + "نوع شبکه نامشخص" + + "داده تلفن همراه" + "Wi-Fi" + "بلوتوث" + "اترنت" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-fi/strings.xml b/service/ServiceConnectivityResources/res/values-fi/strings.xml new file mode 100644 index 0000000000..dd944415c9 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-fi/strings.xml @@ -0,0 +1,39 @@ + + + + "Kirjaudu Wi-Fi-verkkoon" + "Kirjaudu verkkoon" + + "%1$s ei ole yhteydessä internetiin" + "Näytä vaihtoehdot napauttamalla." + "Mobiiliverkko ei ole yhteydessä internetiin" + "Verkko ei ole yhteydessä internetiin" + "Ei pääsyä yksityiselle DNS-palvelimelle" + "%1$s toimii rajoitetulla yhteydellä" + "Yhdistä napauttamalla" + "%1$s otettiin käyttöön" + "%1$s otetaan käyttöön, kun %2$s ei voi muodostaa yhteyttä internetiin. Veloitukset ovat mahdollisia." + "%1$s poistettiin käytöstä ja %2$s otettiin käyttöön." + "tuntematon verkon tyyppi" + + "mobiilidata" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml b/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml new file mode 100644 index 0000000000..02ef50b2c1 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-fr-rCA/strings.xml @@ -0,0 +1,39 @@ + + + + "Connectez-vous au réseau Wi-Fi" + "Connectez-vous au réseau" + + "Le réseau %1$s n\'offre aucun accès à Internet" + "Touchez pour afficher les options" + "Le réseau cellulaire n\'offre aucun accès à Internet" + "Le réseau n\'offre aucun accès à Internet" + "Impossible d\'accéder au serveur DNS privé" + "Le réseau %1$s offre une connectivité limitée" + "Touchez pour vous connecter quand même" + "Passé au réseau %1$s" + "L\'appareil utilise %1$s quand %2$s n\'a pas d\'accès à Internet. Des frais peuvent s\'appliquer." + "Passé du réseau %1$s au %2$s" + "un type de réseau inconnu" + + "données cellulaires" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "RPV" + + diff --git a/service/ServiceConnectivityResources/res/values-fr/strings.xml b/service/ServiceConnectivityResources/res/values-fr/strings.xml new file mode 100644 index 0000000000..08c9d8157a --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-fr/strings.xml @@ -0,0 +1,39 @@ + + + + "Connectez-vous au réseau Wi-Fi" + "Se connecter au réseau" + + "Aucune connexion à Internet pour %1$s" + "Appuyez ici pour afficher des options." + "Le réseau mobile ne dispose d\'aucun accès à Internet" + "Le réseau ne dispose d\'aucun accès à Internet" + "Impossible d\'accéder au serveur DNS privé" + "La connectivité de %1$s est limitée" + "Appuyer pour se connecter quand même" + "Nouveau réseau : %1$s" + "L\'appareil utilise %1$s lorsque %2$s n\'a pas de connexion Internet. Des frais peuvent s\'appliquer." + "Ancien réseau : %1$s. Nouveau réseau : %2$s" + "type de réseau inconnu" + + "données mobiles" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-gl/strings.xml b/service/ServiceConnectivityResources/res/values-gl/strings.xml new file mode 100644 index 0000000000..9f98055ab9 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-gl/strings.xml @@ -0,0 +1,39 @@ + + + + "Inicia sesión na rede wifi" + "Inicia sesión na rede" + + "%1$s non ten acceso a Internet" + "Toca para ver opcións." + "A rede de telefonía móbil non ten acceso a Internet" + "A rede non ten acceso a Internet" + "Non se puido acceder ao servidor DNS privado" + "A conectividade de %1$s é limitada" + "Toca para conectarte de todas formas" + "Cambiouse a: %1$s" + "O dispositivo utiliza %1$s cando %2$s non ten acceso a Internet. Pódense aplicar cargos." + "Cambiouse de %1$s a %2$s" + "un tipo de rede descoñecido" + + "datos móbiles" + "wifi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-gu/strings.xml b/service/ServiceConnectivityResources/res/values-gu/strings.xml new file mode 100644 index 0000000000..4ae5a2c45f --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-gu/strings.xml @@ -0,0 +1,39 @@ + + + + "વાઇ-ફાઇ નેટવર્ક પર સાઇન ઇન કરો" + "નેટવર્ક પર સાઇન ઇન કરો" + + "%1$s ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી" + "વિકલ્પો માટે ટૅપ કરો" + "મોબાઇલ નેટવર્ક કોઈ ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી" + "નેટવર્ક કોઈ ઇન્ટરનેટ ઍક્સેસ ધરાવતું નથી" + "ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી" + "%1$s મર્યાદિત કનેક્ટિવિટી ધરાવે છે" + "છતાં કનેક્ટ કરવા માટે ટૅપ કરો" + "%1$s પર સ્વિચ કર્યું" + "જ્યારે %2$s પાસે કોઈ ઇન્ટરનેટ ઍક્સેસ ન હોય ત્યારે ઉપકરણ %1$sનો ઉપયોગ કરે છે. શુલ્ક લાગુ થઈ શકે છે." + "%1$s પરથી %2$s પર સ્વિચ કર્યું" + "અજાણ્યો નેટવર્ક પ્રકાર" + + "મોબાઇલ ડેટા" + "વાઇ-ફાઇ" + "બ્લૂટૂથ" + "ઇથરનેટ" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-hi/strings.xml b/service/ServiceConnectivityResources/res/values-hi/strings.xml new file mode 100644 index 0000000000..eff1b600e4 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-hi/strings.xml @@ -0,0 +1,39 @@ + + + + "वाई-फ़ाई नेटवर्क में साइन इन करें" + "नेटवर्क में साइन इन करें" + + "%1$s का इंटरनेट नहीं चल रहा है" + "विकल्पों के लिए टैप करें" + "मोबाइल नेटवर्क पर इंटरनेट ऐक्सेस नहीं है" + "इस नेटवर्क पर इंटरनेट ऐक्सेस नहीं है" + "निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता" + "%1$s की कनेक्टिविटी सीमित है" + "फिर भी कनेक्ट करने के लिए टैप करें" + "%1$s पर ले जाया गया" + "%2$s में इंटरनेट की सुविधा नहीं होने पर डिवाइस %1$s का इस्तेमाल करता है. इसके लिए शुल्क लिया जा सकता है." + "%1$s से %2$s पर ले जाया गया" + "अज्ञात नेटवर्क प्रकार" + + "मोबाइल डेटा" + "वाई-फ़ाई" + "ब्लूटूथ" + "ईथरनेट" + "वीपीएन" + + diff --git a/service/ServiceConnectivityResources/res/values-hr/strings.xml b/service/ServiceConnectivityResources/res/values-hr/strings.xml new file mode 100644 index 0000000000..52bfb936ba --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-hr/strings.xml @@ -0,0 +1,39 @@ + + + + "Prijava na Wi-Fi mrežu" + "Prijava na mrežu" + + "%1$s nema pristup internetu" + "Dodirnite za opcije" + "Mobilna mreža nema pristup internetu" + "Mreža nema pristup internetu" + "Nije moguće pristupiti privatnom DNS poslužitelju" + "%1$s ima ograničenu povezivost" + "Dodirnite da biste se ipak povezali" + "Prelazak na drugu mrežu: %1$s" + "Kada %2$s nema pristup internetu, na uređaju se upotrebljava %1$s. Moguća je naplata naknade." + "Mreža je promijenjena: %1$s > %2$s" + "nepoznata vrsta mreže" + + "mobilni podaci" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-hu/strings.xml b/service/ServiceConnectivityResources/res/values-hu/strings.xml new file mode 100644 index 0000000000..2ff59b5173 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-hu/strings.xml @@ -0,0 +1,39 @@ + + + + "Bejelentkezés Wi-Fi hálózatba" + "Bejelentkezés a hálózatba" + + "A(z) %1$s hálózaton nincs internet-hozzáférés" + "Koppintson a beállítások megjelenítéséhez" + "A mobilhálózaton nincs internet-hozzáférés" + "A hálózaton nincs internet-hozzáférés" + "A privát DNS-kiszolgálóhoz nem lehet hozzáférni" + "A(z) %1$s hálózat korlátozott kapcsolatot biztosít" + "Koppintson, ha mindenképpen csatlakozni szeretne" + "Átváltva erre: %1$s" + "%1$s használata, ha nincs internet-hozzáférés %2$s-kapcsolaton keresztül. A szolgáltató díjat számíthat fel." + "Átváltva %1$s-hálózatról erre: %2$s" + "ismeretlen hálózati típus" + + "mobiladatok" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-hy/strings.xml b/service/ServiceConnectivityResources/res/values-hy/strings.xml new file mode 100644 index 0000000000..b35d31c7b6 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-hy/strings.xml @@ -0,0 +1,39 @@ + + + + "Մուտք գործեք Wi-Fi ցանց" + "Մուտք գործեք ցանց" + + "%1$s ցանցը չունի մուտք ինտերնետին" + "Հպեք՝ ընտրանքները տեսնելու համար" + "Բջջային ցանցը չի ապահովում ինտերնետ կապ" + "Ցանցը միացված չէ ինտերնետին" + "Մասնավոր DNS սերվերն անհասանելի է" + "%1$s ցանցի կապը սահմանափակ է" + "Հպեք՝ միանալու համար" + "Անցել է %1$s ցանցի" + "Երբ %2$s ցանցում ինտերնետ կապ չի լինում, սարքն անցնում է %1$s ցանցի: Նման դեպքում կարող են վճարներ գանձվել:" + "%1$s ցանցից անցել է %2$s ցանցի" + "ցանցի անհայտ տեսակ" + + "բջջային ինտերնետ" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-in/strings.xml b/service/ServiceConnectivityResources/res/values-in/strings.xml new file mode 100644 index 0000000000..27d7d89601 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-in/strings.xml @@ -0,0 +1,39 @@ + + + + "Login ke jaringan Wi-Fi" + "Login ke jaringan" + + "%1$s tidak memiliki akses internet" + "Ketuk untuk melihat opsi" + "Jaringan seluler tidak memiliki akses internet" + "Jaringan tidak memiliki akses internet" + "Server DNS pribadi tidak dapat diakses" + "%1$s memiliki konektivitas terbatas" + "Ketuk untuk tetap menyambungkan" + "Dialihkan ke %1$s" + "Perangkat menggunakan %1$s jika %2$s tidak memiliki akses internet. Tarif mungkin berlaku." + "Dialihkan dari %1$s ke %2$s" + "jenis jaringan yang tidak dikenal" + + "data seluler" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-is/strings.xml b/service/ServiceConnectivityResources/res/values-is/strings.xml new file mode 100644 index 0000000000..97f42dcf07 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-is/strings.xml @@ -0,0 +1,39 @@ + + + + "Skrá inn á Wi-Fi net" + "Skrá inn á net" + + "%1$s er ekki með internetaðgang" + "Ýttu til að sjá valkosti" + "Farsímakerfið er ekki tengt við internetið" + "Netkerfið er ekki tengt við internetið" + "Ekki næst í DNS-einkaþjón" + "Tengigeta %1$s er takmörkuð" + "Ýttu til að tengjast samt" + "Skipt yfir á %1$s" + "Tækið notar %1$s þegar %2$s er ekki með internetaðgang. Gjöld kunna að eiga við." + "Skipt úr %1$s yfir í %2$s" + "óþekkt tegund netkerfis" + + "farsímagögn" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-it/strings.xml b/service/ServiceConnectivityResources/res/values-it/strings.xml new file mode 100644 index 0000000000..7836101137 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-it/strings.xml @@ -0,0 +1,39 @@ + + + + "Accedi a rete Wi-Fi" + "Accedi alla rete" + + "%1$s non ha accesso a Internet" + "Tocca per le opzioni" + "La rete mobile non ha accesso a Internet" + "La rete non ha accesso a Internet" + "Non è possibile accedere al server DNS privato" + "%1$s ha una connettività limitata" + "Tocca per connettere comunque" + "Passato a %1$s" + "Il dispositivo utilizza %1$s quando %2$s non ha accesso a Internet. Potrebbero essere applicati costi." + "Passato da %1$s a %2$s" + "tipo di rete sconosciuto" + + "dati mobili" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-iw/strings.xml b/service/ServiceConnectivityResources/res/values-iw/strings.xml new file mode 100644 index 0000000000..f322b99547 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-iw/strings.xml @@ -0,0 +1,39 @@ + + + + "‏היכנס לרשת Wi-Fi" + "היכנס לרשת" + + "ל-%1$s אין גישה לאינטרנט" + "הקש לקבלת אפשרויות" + "לרשת הסלולרית אין גישה לאינטרנט" + "לרשת אין גישה לאינטרנט" + "‏לא ניתן לגשת לשרת DNS הפרטי" + "הקישוריות של %1$s מוגבלת" + "כדי להתחבר למרות זאת יש להקיש" + "מעבר אל %1$s" + "המכשיר משתמש ברשת %1$s כשלרשת %2$s אין גישה לאינטרנט. עשויים לחול חיובים." + "עבר מרשת %1$s לרשת %2$s" + "סוג רשת לא מזוהה" + + "חבילת גלישה" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ja/strings.xml b/service/ServiceConnectivityResources/res/values-ja/strings.xml new file mode 100644 index 0000000000..1aa3216fbc --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ja/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fiネットワークにログイン" + "ネットワークにログインしてください" + + "%1$s はインターネットにアクセスできません" + "タップしてその他のオプションを表示" + "モバイル ネットワークがインターネットに接続されていません" + "ネットワークがインターネットに接続されていません" + "プライベート DNS サーバーにアクセスできません" + "%1$s の接続が制限されています" + "接続するにはタップしてください" + "「%1$s」に切り替えました" + "デバイスで「%2$s」によるインターネット接続ができない場合に「%1$s」を使用します。通信料が発生することがあります。" + "「%1$s」から「%2$s」に切り替えました" + "不明なネットワーク タイプ" + + "モバイルデータ" + "Wi-Fi" + "Bluetooth" + "イーサネット" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ka/strings.xml b/service/ServiceConnectivityResources/res/values-ka/strings.xml new file mode 100644 index 0000000000..61d21b55a0 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ka/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi ქსელთან დაკავშირება" + "ქსელში შესვლა" + + "%1$s-ს არ აქვს ინტერნეტზე წვდომა" + "შეეხეთ ვარიანტების სანახავად" + "მობილურ ქსელს არ აქვს ინტერნეტზე წვდომა" + "ქსელს არ აქვს ინტერნეტზე წვდომა" + "პირად DNS სერვერზე წვდომა შეუძლებელია" + "%1$s-ის კავშირები შეზღუდულია" + "შეეხეთ, თუ მაინც გსურთ დაკავშირება" + "ახლა გამოიყენება %1$s" + "თუ %2$s ინტერნეტთან კავშირს დაკარგავს, მოწყობილობის მიერ %1$s იქნება გამოყენებული, რამაც შეიძლება დამატებითი ხარჯები გამოიწვიოს." + "ახლა გამოიყენება %1$s (გამოიყენებოდა %2$s)" + "უცნობი ტიპის ქსელი" + + "მობილური ინტერნეტი" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-kk/strings.xml b/service/ServiceConnectivityResources/res/values-kk/strings.xml new file mode 100644 index 0000000000..969aaef522 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-kk/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi желісіне кіру" + "Желіге кіру" + + "%1$s желісінің интернетті пайдалану мүмкіндігі шектеулі." + "Опциялар үшін түртіңіз" + "Мобильдік желі интернетке қосылмаған." + "Желі интернетке қосылмаған." + "Жеке DNS серверіне кіру мүмкін емес." + "%1$s желісінің қосылу мүмкіндігі шектеулі." + "Бәрібір жалғау үшін түртіңіз." + "%1$s желісіне ауысты" + "Құрылғы %2$s желісінде интернетпен байланыс жоғалған жағдайда %1$s желісін пайдаланады. Деректер ақысы алынуы мүмкін." + "%1$s желісінен %2$s желісіне ауысты" + "желі түрі белгісіз" + + "мобильдік деректер" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-km/strings.xml b/service/ServiceConnectivityResources/res/values-km/strings.xml new file mode 100644 index 0000000000..da3c337860 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-km/strings.xml @@ -0,0 +1,39 @@ + + + + "ចូល​បណ្ដាញ​វ៉ាយហ្វាយ" + "ចូលទៅបណ្តាញ" + + "%1$s មិនមាន​ការតភ្ជាប់អ៊ីនធឺណិត​ទេ" + "ប៉ះសម្រាប់ជម្រើស" + "បណ្ដាញ​ទូរសព្ទ​ចល័ត​មិនមានការតភ្ជាប់​អ៊ីនធឺណិតទេ" + "បណ្ដាញ​មិនមាន​ការតភ្ជាប់​អ៊ីនធឺណិតទេ" + "មិនអាច​ចូលប្រើ​ម៉ាស៊ីនមេ DNS ឯកជន​បានទេ" + "%1$s មានការតភ្ជាប់​មានកម្រិត" + "មិន​អី​ទេ ចុច​​ភ្ជាប់​ចុះ" + "បានប្តូរទៅ %1$s" + "ឧបករណ៍​ប្រើ %1$s នៅ​ពេល​ដែល %2$s មិនមាន​ការ​តភ្ជាប់​អ៊ីនធឺណិត។ អាច​គិតថ្លៃ​លើការ​ប្រើប្រាស់​ទិន្នន័យ។" + "បានប្តូរពី %1$s ទៅ %2$s" + "ប្រភេទបណ្តាញមិនស្គាល់" + + "ទិន្នន័យ​ទូរសព្ទចល័ត" + "Wi-Fi" + "ប៊្លូធូស" + "អ៊ីសឺរណិត" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-kn/strings.xml b/service/ServiceConnectivityResources/res/values-kn/strings.xml new file mode 100644 index 0000000000..3b5e047731 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-kn/strings.xml @@ -0,0 +1,39 @@ + + + + "ವೈ-ಫೈ ನೆಟ್‍ವರ್ಕ್‌ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ" + "ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ" + + "%1$s ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿಲ್ಲ" + "ಆಯ್ಕೆಗಳಿಗೆ ಟ್ಯಾಪ್ ಮಾಡಿ" + "ಮೊಬೈಲ್ ನೆಟ್‌ವರ್ಕ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿಲ್ಲ" + "ನೆಟ್‌ವರ್ಕ್‌ ಇಂಟರ್ನೆಟ್‌ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿಲ್ಲ" + "ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ" + "%1$s ಸೀಮಿತ ಸಂಪರ್ಕ ಕಲ್ಪಿಸುವಿಕೆಯನ್ನು ಹೊಂದಿದೆ" + "ಹೇಗಾದರೂ ಸಂಪರ್ಕಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ" + "%1$s ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ" + "%2$s ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶ ಹೊಂದಿಲ್ಲದಿರುವಾಗ, ಸಾಧನವು %1$s ಬಳಸುತ್ತದೆ. ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು." + "%1$s ರಿಂದ %2$s ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ" + "ಅಪರಿಚಿತ ನೆಟ್‌ವರ್ಕ್ ಪ್ರಕಾರ" + + "ಮೊಬೈಲ್ ಡೇಟಾ" + "ವೈ-ಫೈ" + "ಬ್ಲೂಟೂತ್‌" + "ಇಥರ್ನೆಟ್" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ko/strings.xml b/service/ServiceConnectivityResources/res/values-ko/strings.xml new file mode 100644 index 0000000000..874bd75970 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ko/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi 네트워크에 로그인" + "네트워크에 로그인" + + "%1$s이(가) 인터넷에 액세스할 수 없습니다." + "탭하여 옵션 보기" + "모바일 네트워크에 인터넷이 연결되어 있지 않습니다." + "네트워크에 인터넷이 연결되어 있지 않습니다." + "비공개 DNS 서버에 액세스할 수 없습니다." + "%1$s에서 연결을 제한했습니다." + "계속 연결하려면 탭하세요." + "%1$s(으)로 전환" + "%2$s(으)로 인터넷에 연결할 수 없는 경우 기기에서 %1$s이(가) 사용됩니다. 요금이 부과될 수 있습니다." + "%1$s에서 %2$s(으)로 전환" + "알 수 없는 네트워크 유형" + + "모바일 데이터" + "Wi-Fi" + "블루투스" + "이더넷" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ky/strings.xml b/service/ServiceConnectivityResources/res/values-ky/strings.xml new file mode 100644 index 0000000000..1ace4dc8ec --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ky/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi түйүнүнө кирүү" + "Тармакка кирүү" + + "%1$s Интернетке туташуусу жок" + "Параметрлерди ачуу үчүн таптап коюңуз" + "Мобилдик Интернет жок" + "Тармактын Интернет жок" + "Жеке DNS сервери жеткиликсиз" + "%1$s байланышы чектелген" + "Баары бир туташуу үчүн таптаңыз" + "%1$s тармагына которуштурулду" + "%2$s тармагы Интернетке туташпай турганда, түзмөгүңүз %1$s тармагын колдонот. Акы алынышы мүмкүн." + "%1$s дегенден %2$s тармагына которуштурулду" + "белгисиз тармак түрү" + + "мобилдик трафик" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-lo/strings.xml b/service/ServiceConnectivityResources/res/values-lo/strings.xml new file mode 100644 index 0000000000..3db497ef45 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-lo/strings.xml @@ -0,0 +1,39 @@ + + + + "ເຂົ້າສູ່ລະບົບເຄືອຂ່າຍ Wi-Fi" + "ລົງຊື່ເຂົ້າເຄືອຂ່າຍ" + + "%1$s ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ" + "ແຕະເພື່ອເບິ່ງຕົວເລືອກ" + "ເຄືອຂ່າຍມືຖືບໍ່ສາມາດເຂົ້າເຖິງອິນເຕີເນັດໄດ້" + "ເຄືອຂ່າຍບໍ່ສາມາດເຂົ້າເຖິງອິນເຕີເນັດໄດ້" + "ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້" + "%1$s ມີການເຊື່ອມຕໍ່ທີ່ຈຳກັດ" + "ແຕະເພື່ອຢືນຢັນການເຊື່ອມຕໍ່" + "ສະຫຼັບໄປໃຊ້ %1$s ແລ້ວ" + "ອຸປະກອນຈະໃຊ້ %1$s ເມື່ອ %2$s ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ. ອາດມີການຮຽກເກັບຄ່າບໍລິການ." + "ສະຫຼັບຈາກ %1$s ໄປໃຊ້ %2$s ແລ້ວ" + "ບໍ່ຮູ້ຈັກປະເພດເຄືອຂ່າຍ" + + "ອິນເຕີເນັດມືຖື" + "Wi-Fi" + "Bluetooth" + "ອີເທີເນັດ" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-lt/strings.xml b/service/ServiceConnectivityResources/res/values-lt/strings.xml new file mode 100644 index 0000000000..c639c1f1a5 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-lt/strings.xml @@ -0,0 +1,39 @@ + + + + "Prisijungti prie „Wi-Fi“ tinklo" + "Prisijungti prie tinklo" + + "„%1$s“ negali pasiekti interneto" + "Palieskite, kad būtų rodomos parinktys." + "Mobiliojo ryšio tinkle nėra prieigos prie interneto" + "Tinkle nėra prieigos prie interneto" + "Privataus DNS serverio negalima pasiekti" + "„%1$s“ ryšys apribotas" + "Palieskite, jei vis tiek norite prisijungti" + "Perjungta į tinklą %1$s" + "Įrenginyje naudojamas kitas tinklas (%1$s), kai dabartiniame tinkle (%2$s) nėra interneto ryšio. Gali būti taikomi mokesčiai." + "Perjungta iš tinklo %1$s į tinklą %2$s" + "nežinomas tinklo tipas" + + "mobiliojo ryšio duomenys" + "Wi-Fi" + "Bluetooth" + "Eternetas" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-lv/strings.xml b/service/ServiceConnectivityResources/res/values-lv/strings.xml new file mode 100644 index 0000000000..5774603bf5 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-lv/strings.xml @@ -0,0 +1,39 @@ + + + + "Pierakstieties Wi-Fi tīklā" + "Pierakstīšanās tīklā" + + "Tīklā %1$s nav piekļuves internetam" + "Pieskarieties, lai skatītu iespējas." + "Mobilajā tīklā nav piekļuves internetam." + "Tīklā nav piekļuves internetam." + "Nevar piekļūt privātam DNS serverim." + "Tīklā %1$s ir ierobežota savienojamība" + "Lai tik un tā izveidotu savienojumu, pieskarieties" + "Pārslēdzās uz tīklu %1$s" + "Kad vienā tīklā (%2$s) nav piekļuves internetam, ierīcē tiek izmantots cits tīkls (%1$s). Var tikt piemērota maksa." + "Pārslēdzās no tīkla %1$s uz tīklu %2$s" + "nezināms tīkla veids" + + "mobilie dati" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-mk/strings.xml b/service/ServiceConnectivityResources/res/values-mk/strings.xml new file mode 100644 index 0000000000..053c7cf93b --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-mk/strings.xml @@ -0,0 +1,39 @@ + + + + "Најавете се на мрежа на Wi-Fi" + "Најавете се на мрежа" + + "%1$s нема интернет-пристап" + "Допрете за опции" + "Мобилната мрежа нема интернет-пристап" + "Мрежата нема интернет-пристап" + "Не може да се пристапи до приватниот DNS-сервер" + "%1$s има ограничена поврзливост" + "Допрете за да се поврзете и покрај тоа" + "Префрлено на %1$s" + "Уредот користи %1$s кога %2$s нема пристап до интернет. Може да се наплатат трошоци." + "Префрлено од %1$s на %2$s" + "непознат тип мрежа" + + "мобилен интернет" + "Wi-Fi" + "Bluetooth" + "Етернет" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ml/strings.xml b/service/ServiceConnectivityResources/res/values-ml/strings.xml new file mode 100644 index 0000000000..3e80cf1cd7 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ml/strings.xml @@ -0,0 +1,39 @@ + + + + "വൈഫൈ നെറ്റ്‌വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക" + "നെറ്റ്‌വർക്കിലേക്ക് സൈൻ ഇൻ ചെയ്യുക" + + "%1$s എന്നതിന് ഇന്റർനെറ്റ് ആക്‌സസ് ഇല്ല" + "ഓപ്ഷനുകൾക്ക് ടാപ്പുചെയ്യുക" + "മൊബെെൽ നെറ്റ്‌വർക്കിന് ഇന്റർനെറ്റ് ആക്‌സസ് ഇല്ല" + "നെറ്റ്‌വർക്കിന് ഇന്റർനെറ്റ് ആക്‌സസ് ഇല്ല" + "സ്വകാര്യ DNS സെർവർ ആക്‌സസ് ചെയ്യാനാവില്ല" + "%1$s എന്നതിന് പരിമിതമായ കണക്റ്റിവിറ്റി ഉണ്ട്" + "ഏതുവിധേനയും കണക്‌റ്റ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക" + "%1$s എന്നതിലേക്ക് മാറി" + "%2$s-ന് ഇന്റർനെറ്റ് ആക്‌സസ് ഇല്ലാത്തപ്പോൾ ഉപകരണം %1$s ഉപയോഗിക്കുന്നു. നിരക്കുകൾ ബാധകമായേക്കാം." + "%1$s നെറ്റ്‌വർക്കിൽ നിന്ന് %2$s നെറ്റ്‌വർക്കിലേക്ക് മാറി" + "തിരിച്ചറിയാനാകാത്ത ഒരു നെറ്റ്‌വർക്ക് തരം" + + "മൊബൈൽ ഡാറ്റ" + "വൈഫൈ" + "Bluetooth" + "ഇതര്‍നെറ്റ്" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-mn/strings.xml b/service/ServiceConnectivityResources/res/values-mn/strings.xml new file mode 100644 index 0000000000..214fc0c1dd --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-mn/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi сүлжээнд нэвтэрнэ үү" + "Сүлжээнд нэвтэрнэ үү" + + "%1$s-д интернэтийн хандалт алга" + "Сонголт хийхийн тулд товшино уу" + "Мобайл сүлжээнд интернэт хандалт байхгүй байна" + "Сүлжээнд интернэт хандалт байхгүй байна" + "Хувийн DNS серверт хандах боломжгүй байна" + "%1$s зарим үйлчилгээнд хандах боломжгүй байна" + "Ямар ч тохиолдолд холбогдохын тулд товших" + "%1$s руу шилжүүлсэн" + "%2$s интернет холболтгүй үед төхөөрөмж %1$s-г ашигладаг. Төлбөр гарч болзошгүй." + "%1$s%2$s руу шилжүүлсэн" + "сүлжээний тодорхойгүй төрөл" + + "мобайл дата" + "Wi-Fi" + "Bluetooth" + "Этернэт" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-mr/strings.xml b/service/ServiceConnectivityResources/res/values-mr/strings.xml new file mode 100644 index 0000000000..c4b19989fc --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-mr/strings.xml @@ -0,0 +1,39 @@ + + + + "वाय-फाय नेटवर्कमध्‍ये साइन इन करा" + "नेटवर्कवर साइन इन करा" + + "%1$s ला इंटरनेट अ‍ॅक्सेस नाही" + "पर्यायांसाठी टॅप करा" + "मोबाइल नेटवर्कला इंटरनेट ॲक्सेस नाही" + "नेटवर्कला इंटरनेट ॲक्सेस नाही" + "खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही" + "%1$s ला मर्यादित कनेक्टिव्हिटी आहे" + "तरीही कनेक्ट करण्यासाठी टॅप करा" + "%1$s वर स्विच केले" + "%2$s कडे इंटरनेटचा अ‍ॅक्सेस नसताना डिव्हाइस %1$s वापरते. शुल्क लागू शकते." + "%1$s वरून %2$s वर स्विच केले" + "अज्ञात नेटवर्क प्रकार" + + "मोबाइल डेटा" + "वाय-फाय" + "ब्लूटूथ" + "इथरनेट" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ms/strings.xml b/service/ServiceConnectivityResources/res/values-ms/strings.xml new file mode 100644 index 0000000000..529d0bd466 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ms/strings.xml @@ -0,0 +1,39 @@ + + + + "Log masuk ke rangkaian Wi-Fi" + "Log masuk ke rangkaian" + + "%1$s tiada akses Internet" + "Ketik untuk mendapatkan pilihan" + "Rangkaian mudah alih tiada akses Internet" + "Rangkaian tiada akses Internet" + "Pelayan DNS peribadi tidak boleh diakses" + "%1$s mempunyai kesambungan terhad" + "Ketik untuk menyambung juga" + "Beralih kepada %1$s" + "Peranti menggunakan %1$s apabila %2$s tiada akses Internet. Bayaran mungkin dikenakan." + "Beralih daripada %1$s kepada %2$s" + "jenis rangkaian tidak diketahui" + + "data mudah alih" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-my/strings.xml b/service/ServiceConnectivityResources/res/values-my/strings.xml new file mode 100644 index 0000000000..50590fa3fc --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-my/strings.xml @@ -0,0 +1,39 @@ + + + + "ဝိုင်ဖိုင်ကွန်ရက်သို့ လက်မှတ်ထိုးဝင်ပါ" + "ကွန်ယက်သို့ လက်မှတ်ထိုးဝင်ရန်" + + "%1$s တွင် အင်တာနက်အသုံးပြုခွင့် မရှိပါ" + "အခြားရွေးချယ်စရာများကိုကြည့်ရန် တို့ပါ" + "မိုဘိုင်းကွန်ရက်တွင် အင်တာနက်ချိတ်ဆက်မှု မရှိပါ" + "ကွန်ရက်တွင် အင်တာနက်အသုံးပြုခွင့် မရှိပါ" + "သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။" + "%1$s တွင် ချိတ်ဆက်မှုကို ကန့်သတ်ထားသည်" + "မည်သို့ပင်ဖြစ်စေ ချိတ်ဆက်ရန် တို့ပါ" + "%1$s သို့ ပြောင်းလိုက်ပြီ" + "%2$s ဖြင့် အင်တာနက် အသုံးမပြုနိုင်သည့်အချိန်တွင် စက်ပစ္စည်းသည် %1$s ကို သုံးပါသည်။ ဒေတာသုံးစွဲခ ကျသင့်နိုင်ပါသည်။" + "%1$s မှ %2$s သို့ ပြောင်းလိုက်ပြီ" + "အမည်မသိကွန်ရက်အမျိုးအစား" + + "မိုဘိုင်းဒေတာ" + "Wi-Fi" + "ဘလူးတုသ်" + "အီသာနက်" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-nb/strings.xml b/service/ServiceConnectivityResources/res/values-nb/strings.xml new file mode 100644 index 0000000000..b89d198f38 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-nb/strings.xml @@ -0,0 +1,39 @@ + + + + "Logg på Wi-Fi-nettverket" + "Logg på nettverk" + + "%1$s har ingen internettilkobling" + "Trykk for å få alternativer" + "Mobilnettverket har ingen internettilgang" + "Nettverket har ingen internettilgang" + "Den private DNS-tjeneren kan ikke nås" + "%1$s har begrenset tilkobling" + "Trykk for å koble til likevel" + "Byttet til %1$s" + "Enheten bruker %1$s når %2$s ikke har Internett-tilgang. Avgifter kan påløpe." + "Byttet fra %1$s til %2$s" + "en ukjent nettverkstype" + + "mobildata" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ne/strings.xml b/service/ServiceConnectivityResources/res/values-ne/strings.xml new file mode 100644 index 0000000000..bdcfa3bfc4 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ne/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi नेटवर्कमा साइन इन गर्नुहोस्" + "सञ्जालमा साइन इन गर्नुहोस्" + + "%1$s को इन्टरनेटमाथि पहुँच छैन" + "विकल्पहरूका लागि ट्याप गर्नुहोस्" + "मोबाइल नेटवर्कको इन्टरनेटमाथि पहुँच छैन" + "नेटवर्कको इन्टरनेटमाथि पहुँच छैन" + "निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन" + "%1$s को जडान सीमित छ" + "जसरी भए पनि जडान गर्न ट्याप गर्नुहोस्" + "%1$s मा बदल्नुहोस्" + "%2$s मार्फत इन्टरनेटमाथि पहुँच राख्न नसकेको अवस्थामा यन्त्रले %1$s प्रयोग गर्दछ। शुल्क लाग्न सक्छ।" + "%1$s बाट %2$s मा परिवर्तन गरियो" + "नेटवर्कको कुनै अज्ञात प्रकार" + + "मोबाइल डेटा" + "Wi-Fi" + "ब्लुटुथ" + "इथरनेट" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-nl/strings.xml b/service/ServiceConnectivityResources/res/values-nl/strings.xml new file mode 100644 index 0000000000..8ecff6e3b5 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-nl/strings.xml @@ -0,0 +1,39 @@ + + + + "Inloggen bij wifi-netwerk" + "Inloggen bij netwerk" + + "%1$s heeft geen internettoegang" + "Tik voor opties" + "Mobiel netwerk heeft geen internettoegang" + "Netwerk heeft geen internettoegang" + "Geen toegang tot privé-DNS-server" + "%1$s heeft beperkte connectiviteit" + "Tik om toch verbinding te maken" + "Overgeschakeld naar %1$s" + "Apparaat gebruikt %1$s wanneer %2$s geen internetverbinding heeft. Er kunnen kosten in rekening worden gebracht." + "Overgeschakeld van %1$s naar %2$s" + "een onbekend netwerktype" + + "mobiele data" + "Wifi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-or/strings.xml b/service/ServiceConnectivityResources/res/values-or/strings.xml new file mode 100644 index 0000000000..6ec1f9d3e4 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-or/strings.xml @@ -0,0 +1,39 @@ + + + + "ୱାଇ-ଫାଇ ନେଟୱର୍କରେ ସାଇନ୍‍-ଇନ୍‍ କରନ୍ତୁ" + "ନେଟ୍‌ୱର୍କରେ ସାଇନ୍‍ ଇନ୍‍ କରନ୍ତୁ" + + "%1$sର ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ" + "ବିକଳ୍ପ ପାଇଁ ଟାପ୍‍ କରନ୍ତୁ" + "ମୋବାଇଲ୍ ନେଟ୍‌ୱାର୍କରେ ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ" + "ନେଟ୍‌ୱାର୍କରେ ଇଣ୍ଟର୍ନେଟ୍ ଆକ୍ସେସ୍ ନାହିଁ" + "ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ" + "%1$sର ସୀମିତ ସଂଯୋଗ ଅଛି" + "ତଥାପି ଯୋଗାଯୋଗ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ" + "%1$sକୁ ବଦଳାଗଲା" + "%2$sର ଇଣ୍ଟରନେଟ୍‍ ଆକ୍ସେସ୍ ନଥିବାବେଳେ ଡିଭାଇସ୍‍ %1$s ବ୍ୟବହାର କରିଥାଏ। ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ।" + "%1$s ରୁ %2$sକୁ ବଦଳାଗଲା" + "ଏକ ଅଜଣା ନେଟ୍‌ୱର୍କ ପ୍ରକାର" + + "ମୋବାଇଲ୍‌ ଡାଟା" + "ୱାଇ-ଫାଇ" + "ବ୍ଲୁଟୁଥ" + "ଇଥରନେଟ୍‌" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-pa/strings.xml b/service/ServiceConnectivityResources/res/values-pa/strings.xml new file mode 100644 index 0000000000..e9481932d6 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-pa/strings.xml @@ -0,0 +1,39 @@ + + + + "ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ" + "ਨੈੱਟਵਰਕ \'ਤੇ ਸਾਈਨ-ਇਨ ਕਰੋ" + + "%1$s ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ" + "ਵਿਕਲਪਾਂ ਲਈ ਟੈਪ ਕਰੋ" + "ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਕੋਲ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਹੈ" + "ਨੈੱਟਵਰਕ ਕੋਲ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਹੈ" + "ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ" + "%1$s ਕੋਲ ਸੀਮਤ ਕਨੈਕਟੀਵਿਟੀ ਹੈ" + "ਫਿਰ ਵੀ ਕਨੈਕਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ" + "ਬਦਲਕੇ %1$s ਲਿਆਂਦਾ ਗਿਆ" + "%2$s ਦੀ ਇੰਟਰਨੈੱਟ \'ਤੇ ਪਹੁੰਚ ਨਾ ਹੋਣ \'ਤੇ ਡੀਵਾਈਸ %1$s ਦੀ ਵਰਤੋਂ ਕਰਦਾ ਹੈ। ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ।" + "%1$s ਤੋਂ ਬਦਲਕੇ %2$s \'ਤੇ ਕੀਤਾ ਗਿਆ" + "ਇੱਕ ਅਗਿਆਤ ਨੈੱਟਵਰਕ ਕਿਸਮ" + + "ਮੋਬਾਈਲ ਡਾਟਾ" + "ਵਾਈ-ਫਾਈ" + "ਬਲੂਟੁੱਥ" + "ਈਥਰਨੈੱਟ" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-pl/strings.xml b/service/ServiceConnectivityResources/res/values-pl/strings.xml new file mode 100644 index 0000000000..038328f0fc --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-pl/strings.xml @@ -0,0 +1,39 @@ + + + + "Zaloguj się w sieci Wi-Fi" + "Zaloguj się do sieci" + + "%1$s nie ma dostępu do internetu" + "Kliknij, by wyświetlić opcje" + "Sieć komórkowa nie ma dostępu do internetu" + "Sieć nie ma dostępu do internetu" + "Brak dostępu do prywatnego serwera DNS" + "%1$s ma ograniczoną łączność" + "Kliknij, by mimo to nawiązać połączenie" + "Zmieniono na połączenie typu %1$s" + "Urządzenie korzysta z połączenia typu %1$s, gdy %2$s nie dostępu do internetu. Mogą zostać naliczone opłaty." + "Przełączono z połączenia typu %1$s na %2$s." + "nieznany typ sieci" + + "mobilna transmisja danych" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml b/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..fe37405ebe --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-pt-rBR/strings.xml @@ -0,0 +1,39 @@ + + + + "Fazer login na rede Wi-Fi" + "Fazer login na rede" + + "%1$s não tem acesso à Internet" + "Toque para ver opções" + "A rede móvel não tem acesso à Internet" + "A rede não tem acesso à Internet" + "Não é possível acessar o servidor DNS privado" + "%1$s tem conectividade limitada" + "Toque para conectar mesmo assim" + "Alternado para %1$s" + "O dispositivo usa %1$s quando %2$s não tem acesso à Internet. Esse serviço pode ser cobrado." + "Alternado de %1$s para %2$s" + "um tipo de rede desconhecido" + + "dados móveis" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml b/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000000..69060f7266 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-pt-rPT/strings.xml @@ -0,0 +1,39 @@ + + + + "Iniciar sessão na rede Wi-Fi" + "Início de sessão na rede" + + "%1$s não tem acesso à Internet" + "Toque para obter mais opções" + "A rede móvel não tem acesso à Internet" + "A rede não tem acesso à Internet" + "Não é possível aceder ao servidor DNS." + "%1$s tem conetividade limitada." + "Toque para ligar mesmo assim." + "Mudou para %1$s" + "O dispositivo utiliza %1$s quando %2$s não tem acesso à Internet. Podem aplicar-se custos." + "Mudou de %1$s para %2$s" + "um tipo de rede desconhecido" + + "dados móveis" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-pt/strings.xml b/service/ServiceConnectivityResources/res/values-pt/strings.xml new file mode 100644 index 0000000000..fe37405ebe --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-pt/strings.xml @@ -0,0 +1,39 @@ + + + + "Fazer login na rede Wi-Fi" + "Fazer login na rede" + + "%1$s não tem acesso à Internet" + "Toque para ver opções" + "A rede móvel não tem acesso à Internet" + "A rede não tem acesso à Internet" + "Não é possível acessar o servidor DNS privado" + "%1$s tem conectividade limitada" + "Toque para conectar mesmo assim" + "Alternado para %1$s" + "O dispositivo usa %1$s quando %2$s não tem acesso à Internet. Esse serviço pode ser cobrado." + "Alternado de %1$s para %2$s" + "um tipo de rede desconhecido" + + "dados móveis" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ro/strings.xml b/service/ServiceConnectivityResources/res/values-ro/strings.xml new file mode 100644 index 0000000000..227b7bed70 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ro/strings.xml @@ -0,0 +1,39 @@ + + + + "Conectați-vă la rețeaua Wi-Fi" + "Conectați-vă la rețea" + + "%1$s nu are acces la internet" + "Atingeți pentru opțiuni" + "Rețeaua mobilă nu are acces la internet" + "Rețeaua nu are acces la internet" + "Serverul DNS privat nu poate fi accesat" + "%1$s are conectivitate limitată" + "Atingeți pentru a vă conecta oricum" + "S-a comutat la %1$s" + "Dispozitivul folosește %1$s când %2$s nu are acces la internet. Se pot aplica taxe." + "S-a comutat de la %1$s la %2$s" + "un tip de rețea necunoscut" + + "date mobile" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ru/strings.xml b/service/ServiceConnectivityResources/res/values-ru/strings.xml new file mode 100644 index 0000000000..18acf81537 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ru/strings.xml @@ -0,0 +1,39 @@ + + + + "Подключение к Wi-Fi" + "Регистрация в сети" + + "Сеть \"%1$s\" не подключена к Интернету" + "Нажмите, чтобы показать варианты." + "Мобильная сеть не подключена к Интернету" + "Сеть не подключена к Интернету" + "Доступа к частному DNS-серверу нет." + "Подключение к сети \"%1$s\" ограничено" + "Нажмите, чтобы подключиться" + "Новое подключение: %1$s" + "Устройство использует %1$s, если подключение к сети %2$s недоступно. Может взиматься плата за передачу данных." + "Устройство отключено от сети %2$s и теперь использует %1$s" + "неизвестный тип сети" + + "мобильный Интернет" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-si/strings.xml b/service/ServiceConnectivityResources/res/values-si/strings.xml new file mode 100644 index 0000000000..6307c2b01b --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-si/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi ජාලයට පුරනය වන්න" + "ජාලයට පුරනය වන්න" + + "%1$s හට අන්තර්ජාල ප්‍රවේශය නැත" + "විකල්ප සඳහා තට්ටු කරන්න" + "ජංගම ජාලවලට අන්තර්ජාල ප්‍රවේශය නැත" + "ජාලයට අන්තර්ජාල ප්‍රවේශය නැත" + "පුද්ගලික DNS සේවාදායකයට ප්‍රවේශ වීමට නොහැකිය" + "%1$s හට සීමිත සබැඳුම් හැකියාවක් ඇත" + "කෙසේ වෙතත් ඉදිරියට යාමට තට්ටු කරන්න" + "%1$s වෙත මාරු විය" + "උපාංගය %1$s %2$s සඳහා අන්තර්ජාල ප්‍රවේශය නැති විට භාවිත කරයි. ගාස්තු අදාළ විය හැකිය." + "%1$s සිට %2$s වෙත මාරු විය" + "නොදන්නා ජාල වර්ගයකි" + + "ජංගම දත්ත" + "Wi-Fi" + "බ්ලූටූත්" + "ඊතර්නෙට්" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-sk/strings.xml b/service/ServiceConnectivityResources/res/values-sk/strings.xml new file mode 100644 index 0000000000..e894fefcf4 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-sk/strings.xml @@ -0,0 +1,39 @@ + + + + "Prihlásiť sa do siete Wi‑Fi" + "Prihlásenie do siete" + + "%1$s nemá prístup k internetu" + "Klepnutím získate možnosti" + "Mobilná sieť nemá prístup k internetu" + "Sieť nemá prístup k internetu" + "K súkromnému serveru DNS sa nepodarilo získať prístup" + "%1$s má obmedzené pripojenie" + "Ak sa chcete aj napriek tomu pripojiť, klepnite" + "Prepnuté na sieť: %1$s" + "Keď %2$s nemá prístup k internetu, zariadenie používa %1$s. Môžu sa účtovať poplatky." + "Prepnuté zo siete %1$s na sieť %2$s" + "neznámy typ siete" + + "mobilné dáta" + "Wi‑Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-sl/strings.xml b/service/ServiceConnectivityResources/res/values-sl/strings.xml new file mode 100644 index 0000000000..954b32480e --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-sl/strings.xml @@ -0,0 +1,39 @@ + + + + "Prijavite se v omrežje Wi-Fi" + "Prijava v omrežje" + + "Omrežje %1$s nima dostopa do interneta" + "Dotaknite se za možnosti" + "Mobilno omrežje nima dostopa do interneta" + "Omrežje nima dostopa do interneta" + "Do zasebnega strežnika DNS ni mogoče dostopati" + "Povezljivost omrežja %1$s je omejena" + "Dotaknite se, da kljub temu vzpostavite povezavo" + "Preklopljeno na omrežje vrste %1$s" + "Naprava uporabi omrežje vrste %1$s, ko omrežje vrste %2$s nima dostopa do interneta. Prenos podatkov se lahko zaračuna." + "Preklopljeno z omrežja vrste %1$s na omrežje vrste %2$s" + "neznana vrsta omrežja" + + "prenos podatkov v mobilnem omrežju" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-sq/strings.xml b/service/ServiceConnectivityResources/res/values-sq/strings.xml new file mode 100644 index 0000000000..bd5d052034 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-sq/strings.xml @@ -0,0 +1,39 @@ + + + + "Identifikohu në rrjetin Wi-Fi" + "Identifikohu në rrjet" + + "%1$s nuk ka qasje në internet" + "Trokit për opsionet" + "Rrjeti celular nuk ka qasje në internet" + "Rrjeti nuk ka qasje në internet" + "Serveri privat DNS nuk mund të qaset" + "%1$s ka lidhshmëri të kufizuar" + "Trokit për t\'u lidhur gjithsesi" + "Kaloi te %1$s" + "Pajisja përdor %1$s kur %2$s nuk ka qasje në internet. Mund të zbatohen tarifa." + "Kaloi nga %1$s te %2$s" + "një lloj rrjeti i panjohur" + + "të dhënat celulare" + "Wi-Fi" + "Bluetooth" + "Eternet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-sr/strings.xml b/service/ServiceConnectivityResources/res/values-sr/strings.xml new file mode 100644 index 0000000000..8bedbffec9 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-sr/strings.xml @@ -0,0 +1,39 @@ + + + + "Пријављивање на WiFi мрежу" + "Пријавите се на мрежу" + + "%1$s нема приступ интернету" + "Додирните за опције" + "Мобилна мрежа нема приступ интернету" + "Мрежа нема приступ интернету" + "Приступ приватном DNS серверу није успео" + "%1$s има ограничену везу" + "Додирните да бисте се ипак повезали" + "Прешли сте на тип мреже %1$s" + "Уређај користи тип мреже %1$s када тип мреже %2$s нема приступ интернету. Можда ће се наплаћивати трошкови." + "Прешли сте са типа мреже %1$s на тип мреже %2$s" + "непознат тип мреже" + + "мобилни подаци" + "WiFi" + "Bluetooth" + "Етернет" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-sv/strings.xml b/service/ServiceConnectivityResources/res/values-sv/strings.xml new file mode 100644 index 0000000000..b3f1763043 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-sv/strings.xml @@ -0,0 +1,39 @@ + + + + "Logga in på ett Wi-Fi-nätverk" + "Logga in på nätverket" + + "%1$s har ingen internetanslutning" + "Tryck för alternativ" + "Mobilnätverket har ingen internetanslutning" + "Nätverket har ingen internetanslutning" + "Det går inte att komma åt den privata DNS-servern." + "%1$s har begränsad anslutning" + "Tryck för att ansluta ändå" + "Byte av nätverk till %1$s" + "%1$s används på enheten när det inte finns internetåtkomst via %2$s. Avgifter kan tillkomma." + "Byte av nätverk från %1$s till %2$s" + "en okänd nätverkstyp" + + "mobildata" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-sw/strings.xml b/service/ServiceConnectivityResources/res/values-sw/strings.xml new file mode 100644 index 0000000000..9674654c69 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-sw/strings.xml @@ -0,0 +1,39 @@ + + + + "Ingia kwa mtandao wa Wi-Fi" + "Ingia katika mtandao" + + "%1$s haina uwezo wa kufikia intaneti" + "Gusa ili upate chaguo" + "Mtandao wa simu hauna uwezo wa kufikia intaneti" + "Mtandao hauna uwezo wa kufikia intaneti" + "Seva ya faragha ya DNS haiwezi kufikiwa" + "%1$s ina muunganisho unaofikia huduma chache." + "Gusa ili uunganishe tu" + "Sasa inatumia %1$s" + "Kifaa hutumia %1$s wakati %2$s haina intaneti. Huenda ukalipishwa." + "Imebadilisha mtandao kutoka %1$s na sasa inatumia %2$s" + "aina ya mtandao isiyojulikana" + + "data ya simu" + "Wi-Fi" + "Bluetooth" + "Ethaneti" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ta/strings.xml b/service/ServiceConnectivityResources/res/values-ta/strings.xml new file mode 100644 index 0000000000..12604cbf1c --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ta/strings.xml @@ -0,0 +1,39 @@ + + + + "வைஃபை நெட்வொர்க்கில் உள்நுழையவும்" + "நெட்வொர்க்கில் உள்நுழையவும்" + + "%1$s நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை" + "விருப்பங்களுக்கு, தட்டவும்" + "மொபைல் நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை" + "நெட்வொர்க்கிற்கு இணைய அணுகல் இல்லை" + "தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது" + "%1$s வரம்பிற்கு உட்பட்ட இணைப்புநிலையைக் கொண்டுள்ளது" + "எப்படியேனும் இணைப்பதற்குத் தட்டவும்" + "%1$sக்கு மாற்றப்பட்டது" + "%2$s நெட்வொர்க்கில் இண்டர்நெட் அணுகல் இல்லாததால், சாதனமானது %1$s நெட்வொர்க்கைப் பயன்படுத்துகிறது. கட்டணங்கள் விதிக்கப்படலாம்." + "%1$s இலிருந்து %2$sக்கு மாற்றப்பட்டது" + "தெரியாத நெட்வொர்க் வகை" + + "மொபைல் டேட்டா" + "வைஃபை" + "புளூடூத்" + "ஈத்தர்நெட்" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-te/strings.xml b/service/ServiceConnectivityResources/res/values-te/strings.xml new file mode 100644 index 0000000000..84a8640b07 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-te/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi నెట్‌వర్క్‌కి సైన్ ఇన్ చేయండి" + "నెట్‌వర్క్‌కి సైన్ ఇన్ చేయండి" + + "%1$sకి ఇంటర్నెట్ యాక్సెస్ లేదు" + "ఎంపికల కోసం నొక్కండి" + "మొబైల్ నెట్‌వర్క్‌కు ఇంటర్నెట్ యాక్సెస్ లేదు" + "నెట్‌వర్క్‌కు ఇంటర్నెట్ యాక్సెస్ లేదు" + "ప్రైవేట్ DNS సర్వర్‌ను యాక్సెస్ చేయడం సాధ్యపడదు" + "%1$s పరిమిత కనెక్టివిటీని కలిగి ఉంది" + "ఏదేమైనా కనెక్ట్ చేయడానికి నొక్కండి" + "%1$sకి మార్చబడింది" + "పరికరం %2$sకి ఇంటర్నెట్ యాక్సెస్ లేనప్పుడు %1$sని ఉపయోగిస్తుంది. ఛార్జీలు వర్తించవచ్చు." + "%1$s నుండి %2$sకి మార్చబడింది" + "తెలియని నెట్‌వర్క్ రకం" + + "మొబైల్ డేటా" + "Wi-Fi" + "బ్లూటూత్" + "ఈథర్‌నెట్" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-th/strings.xml b/service/ServiceConnectivityResources/res/values-th/strings.xml new file mode 100644 index 0000000000..1616e5151d --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-th/strings.xml @@ -0,0 +1,39 @@ + + + + "ลงชื่อเข้าใช้เครือข่าย WiFi" + "ลงชื่อเข้าใช้เครือข่าย" + + "%1$s เข้าถึงอินเทอร์เน็ตไม่ได้" + "แตะเพื่อดูตัวเลือก" + "เครือข่ายมือถือไม่มีการเข้าถึงอินเทอร์เน็ต" + "เครือข่ายไม่มีการเข้าถึงอินเทอร์เน็ต" + "เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้" + "%1$s มีการเชื่อมต่อจำกัด" + "แตะเพื่อเชื่อมต่อ" + "เปลี่ยนเป็น %1$s" + "อุปกรณ์จะใช้ %1$s เมื่อ %2$s เข้าถึงอินเทอร์เน็ตไม่ได้ โดยอาจมีค่าบริการ" + "เปลี่ยนจาก %1$s เป็น %2$s" + "ประเภทเครือข่ายที่ไม่รู้จัก" + + "เน็ตมือถือ" + "Wi-Fi" + "บลูทูธ" + "อีเทอร์เน็ต" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-tl/strings.xml b/service/ServiceConnectivityResources/res/values-tl/strings.xml new file mode 100644 index 0000000000..3bf1ce494a --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-tl/strings.xml @@ -0,0 +1,39 @@ + + + + "Mag-sign in sa Wi-Fi network" + "Mag-sign in sa network" + + "Walang access sa internet ang %1$s" + "I-tap para sa mga opsyon" + "Walang access sa internet ang mobile network" + "Walang access sa internet ang network" + "Hindi ma-access ang pribadong DNS server" + "Limitado ang koneksyon ng %1$s" + "I-tap para kumonekta pa rin" + "Lumipat sa %1$s" + "Ginagamit ng device ang %1$s kapag walang access sa internet ang %2$s. Maaaring may mga malapat na singilin." + "Lumipat sa %2$s mula sa %1$s" + "isang hindi kilalang uri ng network" + + "mobile data" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-tr/strings.xml b/service/ServiceConnectivityResources/res/values-tr/strings.xml new file mode 100644 index 0000000000..5c326e5eee --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-tr/strings.xml @@ -0,0 +1,39 @@ + + + + "Kablosuz ağda oturum açın" + "Ağda oturum açın" + + "%1$s ağının internet bağlantısı yok" + "Seçenekler için dokunun" + "Mobil ağın internet bağlantısı yok" + "Ağın internet bağlantısı yok" + "Gizli DNS sunucusuna erişilemiyor" + "%1$s sınırlı bağlantıya sahip" + "Yine de bağlanmak için dokunun" + "%1$s ağına geçildi" + "%2$s ağının internet erişimi olmadığında cihaz %1$s ağını kullanır. Bunun için ödeme alınabilir." + "%1$s ağından %2$s ağına geçildi" + "bilinmeyen ağ türü" + + "mobil veri" + "Kablosuz" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-uk/strings.xml b/service/ServiceConnectivityResources/res/values-uk/strings.xml new file mode 100644 index 0000000000..d1382da924 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-uk/strings.xml @@ -0,0 +1,39 @@ + + + + "Вхід у мережу Wi-Fi" + "Вхід у мережу" + + "Мережа %1$s не має доступу до Інтернету" + "Торкніться, щоб відкрити опції" + "Мобільна мережа не має доступу до Інтернету" + "Мережа не має доступу до Інтернету" + "Немає доступу до приватного DNS-сервера" + "Підключення до мережі %1$s обмежено" + "Натисніть, щоб усе одно підключитися" + "Пристрій перейшов на мережу %1$s" + "Коли мережа %2$s не має доступу до Інтернету, використовується %1$s. Може стягуватися плата." + "Пристрій перейшов з мережі %1$s на мережу %2$s" + "невідомий тип мережі" + + "мобільне передавання даних" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "Мережа VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-ur/strings.xml b/service/ServiceConnectivityResources/res/values-ur/strings.xml new file mode 100644 index 0000000000..3c031ad318 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-ur/strings.xml @@ -0,0 +1,39 @@ + + + + "‏Wi-Fi نیٹ ورک میں سائن ان کریں" + "نیٹ ورک میں سائن ان کریں" + + "%1$s کو انٹرنیٹ تک رسائی حاصل نہیں ہے" + "اختیارات کیلئے تھپتھپائیں" + "موبائل نیٹ ورک کو انٹرنیٹ تک رسائی حاصل نہیں ہے" + "نیٹ ورک کو انٹرنیٹ تک رسائی حاصل نہیں ہے" + "‏نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی" + "%1$s کی کنیکٹوٹی محدود ہے" + "بہر حال منسلک کرنے کے لیے تھپتھپائیں" + "%1$s پر سوئچ ہو گیا" + "جب %2$s کو انٹرنیٹ تک رسائی نہیں ہوتی ہے تو آلہ %1$s کا استعمال کرتا ہے۔ چارجز لاگو ہو سکتے ہیں۔" + "%1$s سے %2$s پر سوئچ ہو گیا" + "نیٹ ورک کی نامعلوم قسم" + + "موبائل ڈیٹا" + "Wi-Fi" + "بلوٹوتھ" + "ایتھرنیٹ" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-uz/strings.xml b/service/ServiceConnectivityResources/res/values-uz/strings.xml new file mode 100644 index 0000000000..7518db3a7b --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-uz/strings.xml @@ -0,0 +1,39 @@ + + + + "Wi-Fi tarmoqqa kirish" + "Tarmoqqa kirish" + + "%1$s nomli tarmoqda internetga ruxsati yoʻq" + "Variantlarni ko‘rsatish uchun bosing" + "Mobil tarmoq internetga ulanmagan" + "Tarmoq internetga ulanmagan" + "Xususiy DNS server ishlamayapti" + "%1$s nomli tarmoqda aloqa cheklangan" + "Baribir ulash uchun bosing" + "Yangi ulanish: %1$s" + "Agar %2$s tarmoqda internet uzilsa, qurilma %1$sga ulanadi. Sarflangan trafik uchun haq olinishi mumkin." + "%1$s tarmog‘idan %2$s tarmog‘iga o‘tildi" + "noma’lum tarmoq turi" + + "mobil internet" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-vi/strings.xml b/service/ServiceConnectivityResources/res/values-vi/strings.xml new file mode 100644 index 0000000000..d2284d4905 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-vi/strings.xml @@ -0,0 +1,39 @@ + + + + "Đăng nhập vào mạng Wi-Fi" + "Đăng nhập vào mạng" + + "%1$s không có quyền truy cập Internet" + "Nhấn để biết tùy chọn" + "Mạng di động không có quyền truy cập Internet" + "Mạng không có quyền truy cập Internet" + "Không thể truy cập máy chủ DNS riêng tư" + "%1$s có khả năng kết nối giới hạn" + "Nhấn để tiếp tục kết nối" + "Đã chuyển sang %1$s" + "Thiết bị sử dụng %1$s khi %2$s không có quyền truy cập Internet. Bạn có thể phải trả phí." + "Đã chuyển từ %1$s sang %2$s" + "loại mạng không xác định" + + "dữ liệu di động" + "Wi-Fi" + "Bluetooth" + "Ethernet" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..813482b499 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-zh-rCN/strings.xml @@ -0,0 +1,39 @@ + + + + "登录到WLAN网络" + "登录到网络" + + "%1$s 无法访问互联网" + "点按即可查看相关选项" + "此移动网络无法访问互联网" + "此网络无法访问互联网" + "无法访问私人 DNS 服务器" + "%1$s 的连接受限" + "点按即可继续连接" + "已切换至%1$s" + "设备会在%2$s无法访问互联网时使用%1$s(可能需要支付相应的费用)。" + "已从%1$s切换至%2$s" + "未知网络类型" + + "移动数据" + "WLAN" + "蓝牙" + "以太网" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml new file mode 100644 index 0000000000..676404fe2c --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-zh-rHK/strings.xml @@ -0,0 +1,39 @@ + + + + "登入 Wi-Fi 網絡" + "登入網絡" + + "%1$s未有連接至互聯網" + "輕按即可查看選項" + "流動網絡並未連接互聯網" + "網絡並未連接互聯網" + "無法存取私人 DNS 伺服器" + "%1$s連線受限" + "仍要輕按以連結至此網絡" + "已切換至%1$s" + "裝置會在 %2$s 無法連線至互聯網時使用%1$s (可能需要支付相關費用)。" + "已從%1$s切換至%2$s" + "不明網絡類型" + + "流動數據" + "Wi-Fi" + "藍牙" + "以太網" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml b/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..f355138bd6 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-zh-rTW/strings.xml @@ -0,0 +1,39 @@ + + + + "登入 Wi-Fi 網路" + "登入網路" + + "%1$s 沒有網際網路連線" + "輕觸即可查看選項" + "這個行動網路沒有網際網路連線" + "這個網路沒有網際網路連線" + "無法存取私人 DNS 伺服器" + "%1$s 的連線能力受限" + "輕觸即可繼續連線" + "已切換至%1$s" + "裝置會在無法連上「%2$s」時切換至「%1$s」(可能需要支付相關費用)。" + "已從 %1$s 切換至%2$s" + "不明的網路類型" + + "行動數據" + "Wi-Fi" + "藍牙" + "乙太網路" + "VPN" + + diff --git a/service/ServiceConnectivityResources/res/values-zu/strings.xml b/service/ServiceConnectivityResources/res/values-zu/strings.xml new file mode 100644 index 0000000000..55fefb7939 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-zu/strings.xml @@ -0,0 +1,39 @@ + + + + "Ngena ngemvume kunethiwekhi ye-Wi-Fi" + "Ngena ngemvume kunethiwekhi" + + "I-%1$s ayinakho ukufinyelela kwe-inthanethi" + "Thepha ukuze uthole izinketho" + "Inethiwekhi yeselula ayinakho ukufinyelela kwe-inthanethi" + "Inethiwekhi ayinakho ukufinyelela kwenethiwekhi" + "Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa" + "I-%1$s inokuxhumeka okukhawulelwe" + "Thepha ukuze uxhume noma kunjalo" + "Kushintshelwe ku-%1$s" + "Idivayisi isebenzisa i-%1$s uma i-%2$s inganakho ukufinyelela kwe-inthanethi. Kungasebenza izindleko." + "Kushintshelewe kusuka ku-%1$s kuya ku-%2$s" + "uhlobo olungaziwa lwenethiwekhi" + + "idatha yeselula" + "I-Wi-Fi" + "I-Bluetooth" + "I-Ethernet" + "I-VPN" + + diff --git a/service/ServiceConnectivityResources/res/values/strings.xml b/service/ServiceConnectivityResources/res/values/strings.xml index 2c7b992650..7a9cf57afc 100644 --- a/service/ServiceConnectivityResources/res/values/strings.xml +++ b/service/ServiceConnectivityResources/res/values/strings.xml @@ -14,9 +14,63 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + System Connectivity Resources - \ No newline at end of file + + + Sign in to Wi-Fi network + + + Sign in to network + + + %1$s + + + %1$s has no internet access + + + Tap for options + + + Mobile network has no internet access + + + Network has no internet access + + + Private DNS server cannot be accessed + + + %1$s has limited connectivity + + + Tap to connect anyway + + + Switched to %1$s + + + Device uses %1$s when %2$s has no internet access. Charges may apply. + + + Switched from %1$s to %2$s + + + + mobile data + Wi-Fi + Bluetooth + Ethernet + VPN + + + + + + an unknown network type + + From e62e7ff645cf7d102fcbeec399249ddfa047c2ee Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 18 Mar 2021 14:23:12 +0900 Subject: [PATCH 134/232] Add multipath preference, background status API Add APIs for getMultipathPreference and getRestrictBackgroundStatus. Both are used by Connectivity to back the external ConnectivityManager.getRestrictBackgroundStatus, and ConnectivityManager.getMultipathPreference APIs. Test: atest CtsNetTestCases atest ConnectivityServiceTests atest NetworkPolicyManagerServiceTest Bug: 176289731 Change-Id: I8a03162b2f6691086bb64e75ffd354cdfca7f86a Merged-In: I8a03162b2f6691086bb64e75ffd354cdfca7f86a --- .../src/android/net/ConnectivityManager.java | 15 +-------------- .../src/android/net/IConnectivityManager.aidl | 2 ++ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index e32622391c..001e3a72d2 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -62,7 +62,6 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; -import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.UserHandle; import android.provider.Settings; @@ -842,7 +841,6 @@ public class ConnectivityManager { private final Context mContext; - private INetworkPolicyManager mNPManager; private final TetheringManager mTetheringManager; /** @@ -4794,17 +4792,6 @@ public class ConnectivityManager { public @interface RestrictBackgroundStatus { } - private INetworkPolicyManager getNetworkPolicyManager() { - synchronized (this) { - if (mNPManager != null) { - return mNPManager; - } - mNPManager = INetworkPolicyManager.Stub.asInterface(ServiceManager - .getService(Context.NETWORK_POLICY_SERVICE)); - return mNPManager; - } - } - /** * Determines if the calling application is subject to metered network restrictions while * running on background. @@ -4815,7 +4802,7 @@ public class ConnectivityManager { */ public @RestrictBackgroundStatus int getRestrictBackgroundStatus() { try { - return getNetworkPolicyManager().getRestrictBackgroundByCaller(); + return mService.getRestrictBackgroundStatusByCaller(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index d83cc163b5..98f3d40c0b 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -220,4 +220,6 @@ interface IConnectivityManager void setProfileNetworkPreference(in UserHandle profile, int preference, in IOnCompleteListener listener); + + int getRestrictBackgroundStatusByCaller(); } From 5245c4c0f00eab5bc4408bb36b349d9f0d792610 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 17 Mar 2021 23:14:53 +0900 Subject: [PATCH 135/232] Migrate framework-connectivity internal resources Use ServiceConnectivityResources instead. Start by creating resources in the ServiceConnectivityResources package to match the internal configuration, and common overlays. Bug: 182125649 Test: device boots, has connectivity Change-Id: I77a3efca2cd644f9828db1ed5d3cae8070fb8363 Merged-In: I77a3efca2cd644f9828db1ed5d3cae8070fb8363 --- .../android/net/ConnectivityResources.java | 108 ++++++++++++++++++ .../src/android/net/apf/ApfCapabilities.java | 48 +++++++- .../src/android/net/util/KeepaliveUtils.java | 10 +- .../net/util/MultinetworkPolicyTracker.java | 18 ++- .../ServiceConnectivityResources/Android.bp | 2 +- .../res/values-mcc204-mnc04/config.xml | 27 +++++ .../res/values-mcc310-mnc004/config.xml | 27 +++++ .../res/values-mcc311-mnc480/config.xml | 27 +++++ .../res/values/config.xml | 39 ++++++- .../res/values/overlayable.xml | 5 + 10 files changed, 296 insertions(+), 15 deletions(-) create mode 100644 framework/src/android/net/ConnectivityResources.java create mode 100644 service/ServiceConnectivityResources/res/values-mcc204-mnc04/config.xml create mode 100644 service/ServiceConnectivityResources/res/values-mcc310-mnc004/config.xml create mode 100644 service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml diff --git a/framework/src/android/net/ConnectivityResources.java b/framework/src/android/net/ConnectivityResources.java new file mode 100644 index 0000000000..18f0de0cc9 --- /dev/null +++ b/framework/src/android/net/ConnectivityResources.java @@ -0,0 +1,108 @@ +/* + * 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 android.net; + +import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.List; + +/** + * Utility to obtain the {@link com.android.server.ConnectivityService} {@link Resources}, in the + * ServiceConnectivityResources APK. + * @hide + */ +public class ConnectivityResources { + private static final String RESOURCES_APK_INTENT = + "com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK"; + private static final String RES_PKG_DIR = "/apex/com.android.tethering/"; + + @NonNull + private final Context mContext; + + @Nullable + private Context mResourcesContext = null; + + @Nullable + private static Context sTestResourcesContext = null; + + public ConnectivityResources(Context context) { + mContext = context; + } + + /** + * Convenience method to mock all resources for the duration of a test. + * + * Call with a null context to reset after the test. + */ + @VisibleForTesting + public static void setResourcesContextForTest(@Nullable Context testContext) { + sTestResourcesContext = testContext; + } + + /** + * Get the {@link Context} of the resources package. + */ + public synchronized Context getResourcesContext() { + if (sTestResourcesContext != null) { + return sTestResourcesContext; + } + + if (mResourcesContext != null) { + return mResourcesContext; + } + + final List pkgs = mContext.getPackageManager() + .queryIntentActivities(new Intent(RESOURCES_APK_INTENT), MATCH_SYSTEM_ONLY); + pkgs.removeIf(pkg -> !pkg.activityInfo.applicationInfo.sourceDir.startsWith(RES_PKG_DIR)); + if (pkgs.size() > 1) { + Log.wtf(ConnectivityResources.class.getSimpleName(), + "More than one package found: " + pkgs); + } + if (pkgs.isEmpty()) { + throw new IllegalStateException("No connectivity resource package found"); + } + + final Context pkgContext; + try { + pkgContext = mContext.createPackageContext( + pkgs.get(0).activityInfo.applicationInfo.packageName, 0 /* flags */); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("Resolved package not found", e); + } + + mResourcesContext = pkgContext; + return pkgContext; + } + + /** + * Get the {@link Resources} of the ServiceConnectivityResources APK. + */ + public Resources get() { + return getResourcesContext().getResources(); + } +} diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java index bf5b26e278..85b24713f2 100644 --- a/framework/src/android/net/apf/ApfCapabilities.java +++ b/framework/src/android/net/apf/ApfCapabilities.java @@ -19,12 +19,12 @@ package android.net.apf; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Context; import android.content.res.Resources; +import android.net.ConnectivityResources; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.R; - /** * APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible * way to drop unwanted network packets to save power. @@ -36,6 +36,8 @@ import com.android.internal.R; */ @SystemApi public final class ApfCapabilities implements Parcelable { + private static ConnectivityResources sResources; + /** * Version of APF instruction set supported for packet filtering. 0 indicates no support for * packet filtering using APF programs. @@ -65,6 +67,14 @@ public final class ApfCapabilities implements Parcelable { apfPacketFormat = in.readInt(); } + @NonNull + private static synchronized ConnectivityResources getResources(@NonNull Context ctx) { + if (sResources == null) { + sResources = new ConnectivityResources(ctx); + } + return sResources; + } + @Override public int describeContents() { @@ -121,13 +131,43 @@ public final class ApfCapabilities implements Parcelable { * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. */ public static boolean getApfDrop8023Frames() { - return Resources.getSystem().getBoolean(R.bool.config_apfDrop802_3Frames); + // TODO(b/183076074): remove reading resources from system resources + final Resources systemRes = Resources.getSystem(); + final int id = systemRes.getIdentifier("config_apfDrop802_3Frames", "bool", "android"); + return systemRes.getBoolean(id); + } + + /** + * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. + * @hide + */ + public static boolean getApfDrop8023Frames(@NonNull Context context) { + final ConnectivityResources res = getResources(context); + // TODO(b/183076074): use R.bool.config_apfDrop802_3Frames directly + final int id = res.get().getIdentifier("config_apfDrop802_3Frames", "bool", + res.getResourcesContext().getPackageName()); + return res.get().getBoolean(id); } /** * @return An array of denylisted EtherType, packets with EtherTypes within it will be dropped. */ public static @NonNull int[] getApfEtherTypeBlackList() { - return Resources.getSystem().getIntArray(R.array.config_apfEthTypeBlackList); + // TODO(b/183076074): remove reading resources from system resources + final Resources systemRes = Resources.getSystem(); + final int id = systemRes.getIdentifier("config_apfEthTypeBlackList", "array", "android"); + return systemRes.getIntArray(id); + } + + /** + * @return An array of denylisted EtherType, packets with EtherTypes within it will be dropped. + * @hide + */ + public static @NonNull int[] getApfEtherTypeDenyList(@NonNull Context context) { + final ConnectivityResources res = getResources(context); + // TODO(b/183076074): use R.array.config_apfEthTypeDenyList directly + final int id = res.get().getIdentifier("config_apfEthTypeDenyList", "array", + res.getResourcesContext().getPackageName()); + return res.get().getIntArray(id); } } diff --git a/framework/src/android/net/util/KeepaliveUtils.java b/framework/src/android/net/util/KeepaliveUtils.java index bfc4563fbf..8d7a0b3d02 100644 --- a/framework/src/android/net/util/KeepaliveUtils.java +++ b/framework/src/android/net/util/KeepaliveUtils.java @@ -19,12 +19,11 @@ package android.net.util; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; +import android.net.ConnectivityResources; import android.net.NetworkCapabilities; import android.text.TextUtils; import android.util.AndroidRuntimeException; -import com.android.internal.R; - /** * Collection of utilities for socket keepalive offload. * @@ -52,8 +51,11 @@ public final class KeepaliveUtils { public static int[] getSupportedKeepalives(@NonNull Context context) { String[] res = null; try { - res = context.getResources().getStringArray( - R.array.config_networkSupportedKeepaliveCount); + final ConnectivityResources connRes = new ConnectivityResources(context); + // TODO: use R.id.config_networkSupportedKeepaliveCount directly + final int id = connRes.get().getIdentifier("config_networkSupportedKeepaliveCount", + "array", connRes.getResourcesContext().getPackageName()); + res = new ConnectivityResources(context).get().getStringArray(id); } catch (Resources.NotFoundException unused) { } if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource"); diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java index 6a49aa2576..0b42a00369 100644 --- a/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.database.ContentObserver; +import android.net.ConnectivityResources; import android.net.Uri; import android.os.Handler; import android.provider.Settings; @@ -35,7 +36,6 @@ import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.Log; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; @@ -64,6 +64,7 @@ public class MultinetworkPolicyTracker { private static String TAG = MultinetworkPolicyTracker.class.getSimpleName(); private final Context mContext; + private final ConnectivityResources mResources; private final Handler mHandler; private final Runnable mAvoidBadWifiCallback; private final List mSettingsUris; @@ -107,6 +108,7 @@ public class MultinetworkPolicyTracker { public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) { mContext = ctx; + mResources = new ConnectivityResources(ctx); mHandler = handler; mAvoidBadWifiCallback = avoidBadWifiCallback; mSettingsUris = Arrays.asList( @@ -160,12 +162,16 @@ public class MultinetworkPolicyTracker { * Whether the device or carrier configuration disables avoiding bad wifi by default. */ public boolean configRestrictsAvoidBadWifi() { - return (getResourcesForActiveSubId().getInteger(R.integer.config_networkAvoidBadWifi) == 0); + // TODO: use R.integer.config_networkAvoidBadWifi directly + final int id = mResources.get().getIdentifier("config_networkAvoidBadWifi", + "integer", mResources.getResourcesContext().getPackageName()); + return (getResourcesForActiveSubId().getInteger(id) == 0); } @NonNull private Resources getResourcesForActiveSubId() { - return SubscriptionManager.getResourcesForSubId(mContext, mActiveSubId); + return SubscriptionManager.getResourcesForSubId( + mResources.getResourcesContext(), mActiveSubId); } /** @@ -205,8 +211,10 @@ public class MultinetworkPolicyTracker { * The default (device and carrier-dependent) value for metered multipath preference. */ public int configMeteredMultipathPreference() { - return mContext.getResources().getInteger( - R.integer.config_networkMeteredMultipathPreference); + // TODO: use R.integer.config_networkMeteredMultipathPreference directly + final int id = mResources.get().getIdentifier("config_networkMeteredMultipathPreference", + "integer", mResources.getResourcesContext().getPackageName()); + return mResources.get().getInteger(id); } public void updateMeteredMultipathPreference() { diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp index f2446b7f7e..fa4501ac7f 100644 --- a/service/ServiceConnectivityResources/Android.bp +++ b/service/ServiceConnectivityResources/Android.bp @@ -21,7 +21,7 @@ package { android_app { name: "ServiceConnectivityResources", - sdk_version: "system_current", + sdk_version: "module_current", resource_dirs: [ "res", ], diff --git a/service/ServiceConnectivityResources/res/values-mcc204-mnc04/config.xml b/service/ServiceConnectivityResources/res/values-mcc204-mnc04/config.xml new file mode 100644 index 0000000000..7e7025fb04 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-mcc204-mnc04/config.xml @@ -0,0 +1,27 @@ + + + + + + + 0 + \ No newline at end of file diff --git a/service/ServiceConnectivityResources/res/values-mcc310-mnc004/config.xml b/service/ServiceConnectivityResources/res/values-mcc310-mnc004/config.xml new file mode 100644 index 0000000000..7e7025fb04 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-mcc310-mnc004/config.xml @@ -0,0 +1,27 @@ + + + + + + + 0 + \ No newline at end of file diff --git a/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml b/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml new file mode 100644 index 0000000000..7e7025fb04 --- /dev/null +++ b/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml @@ -0,0 +1,27 @@ + + + + + + + 0 + \ No newline at end of file diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml index 06c81921fd..71674e4dc6 100644 --- a/service/ServiceConnectivityResources/res/values/config.xml +++ b/service/ServiceConnectivityResources/res/values/config.xml @@ -52,4 +52,41 @@ 12,60000 - \ No newline at end of file + + true + + + + 0x88A2 + 0x88A4 + 0x88B8 + 0x88CD + 0x88E3 + + + + + + 0,1 + 1,3 + + + + + 0 + + + 1 + + diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml index da8aee5627..25e19cedbb 100644 --- a/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -21,6 +21,11 @@ + + + + + From 43dea2835ed82cf0116acd41c9fab896d14533e9 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 19 Feb 2021 12:53:54 +0900 Subject: [PATCH 136/232] Move connectivity AIDLs to android.net java_sdk_libraries and apexes need to contain bootclasspath classes under predefined packages. Tethering currently uses android.net, so make sure all the connectivity bootclasspath classes are under android.net. This avoids maintaining two packages for the tethering APEX, where com.android.connectivity.aidl is only used by internal AIDL files. Bug: 182984842 Test: m Change-Id: I611f1941698c574e37aea912ee76dadc8b32e41a Merged-In: I611f1941698c574e37aea912ee76dadc8b32e41a --- framework/src/android/net/ConnectivityManager.java | 1 - framework/src/android/net/IConnectivityManager.aidl | 3 +-- .../connectivity/aidl => android/net}/INetworkAgent.aidl | 4 ++-- .../aidl => android/net}/INetworkAgentRegistry.aidl | 2 +- framework/src/android/net/NetworkAgent.java | 2 -- 5 files changed, 4 insertions(+), 8 deletions(-) rename framework/src/{com/android/connectivity/aidl => android/net}/INetworkAgent.aidl (95%) rename framework/src/{com/android/connectivity/aidl => android/net}/INetworkAgentRegistry.aidl (97%) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 001e3a72d2..a5e9f31a5b 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -73,7 +73,6 @@ import android.util.Log; import android.util.Range; import android.util.SparseIntArray; -import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 98f3d40c0b..3300fa8fd1 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; +import android.net.INetworkAgent; import android.net.IOnCompleteListener; import android.net.INetworkActivityListener; import android.net.IQosCallback; @@ -45,8 +46,6 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import android.os.UserHandle; -import com.android.connectivity.aidl.INetworkAgent; - /** * Interface that answers queries about, and allows changing, the * state of network connectivity. diff --git a/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl similarity index 95% rename from framework/src/com/android/connectivity/aidl/INetworkAgent.aidl rename to framework/src/android/net/INetworkAgent.aidl index 64b556720c..1f66e18717 100644 --- a/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl +++ b/framework/src/android/net/INetworkAgent.aidl @@ -13,13 +13,13 @@ * See the License for the specific language governing perNmissions and * limitations under the License. */ -package com.android.connectivity.aidl; +package android.net; import android.net.NattKeepalivePacketData; import android.net.QosFilterParcelable; import android.net.TcpKeepalivePacketData; -import com.android.connectivity.aidl.INetworkAgentRegistry; +import android.net.INetworkAgentRegistry; /** * Interface to notify NetworkAgent of connectivity events. diff --git a/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl similarity index 97% rename from framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl rename to framework/src/android/net/INetworkAgentRegistry.aidl index 18d26a7e4b..c5464d3241 100644 --- a/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl +++ b/framework/src/android/net/INetworkAgentRegistry.aidl @@ -13,7 +13,7 @@ * See the License for the specific language governing perNmissions and * limitations under the License. */ -package com.android.connectivity.aidl; +package android.net; import android.net.LinkProperties; import android.net.Network; diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index a127c6f6de..1416bb9775 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -34,8 +34,6 @@ import android.os.RemoteException; import android.telephony.data.EpsBearerQosSessionAttributes; import android.util.Log; -import com.android.connectivity.aidl.INetworkAgent; -import com.android.connectivity.aidl.INetworkAgentRegistry; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; From f96b26606a1f7d591f2ddfb41e5a41ff28bc971f Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Sun, 14 Mar 2021 15:28:10 +0900 Subject: [PATCH 137/232] Add connectivity protos to framework-connectivity The protos are built separately by framework-connectivity from framework protos, keeping only android.net protos for the connectivity jar. Bug: 171860710 Test: m framework-connectivity.impl Change-Id: I2c4a37ff2ee9e8efde49885feeafa27dcff7ca2c Merged-In: I2c4a37ff2ee9e8efde49885feeafa27dcff7ca2c --- framework/Android.bp | 20 ++++++++++++++++++++ framework/jarjar-rules-proto.txt | 3 +++ framework/jarjar-rules.txt | 3 +++ 3 files changed, 26 insertions(+) create mode 100644 framework/jarjar-rules-proto.txt diff --git a/framework/Android.bp b/framework/Android.bp index 86433e1c38..017ff51f36 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -23,6 +23,25 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +java_library { + name: "framework-connectivity-protos", + proto: { + type: "nano", + }, + srcs: [ + // TODO: consider moving relevant .proto files directly to the module directory + ":framework-javastream-protos", + ], + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], + jarjar_rules: "jarjar-rules-proto.txt", + visibility: [ + "//visibility:private", + ], +} + filegroup { name: "framework-connectivity-internal-sources", srcs: [ @@ -111,6 +130,7 @@ java_library { "ServiceConnectivityResources", ], static_libs: [ + "framework-connectivity-protos", "net-utils-device-common", ], jarjar_rules: "jarjar-rules.txt", diff --git a/framework/jarjar-rules-proto.txt b/framework/jarjar-rules-proto.txt new file mode 100644 index 0000000000..37b4dec1c3 --- /dev/null +++ b/framework/jarjar-rules-proto.txt @@ -0,0 +1,3 @@ +keep android.net.NetworkCapabilitiesProto +keep android.net.NetworkProto +keep android.net.NetworkRequestProto diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt index 381a4ac875..0959840f2d 100644 --- a/framework/jarjar-rules.txt +++ b/framework/jarjar-rules.txt @@ -5,3 +5,6 @@ zap android.annotation.** zap com.android.net.module.annotation.** zap com.android.internal.annotations.** +rule android.net.NetworkCapabilitiesProto* android.net.connectivity.proto.NetworkCapabilitiesProto@1 +rule android.net.NetworkProto* android.net.connectivity.proto.NetworkProto@1 +rule android.net.NetworkRequestProto* android.net.connectivity.proto.NetworkRequestProto@1 From 4b79f7bd56aa187cc3c99c061fb77872148f2f02 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 19 Mar 2021 17:45:27 +0900 Subject: [PATCH 138/232] Use module resources in NetworkNotificationManager. Also make getTransportName non-static so it can access the module resources. Also fix a duplicate comment in a resource file. Bug: 183097033 Test: atest FrameworksNetTests Test: connected to Wi-Fi with no Internet, observed notification Change-Id: Ic0d24d36af0b87153d527083f8964ddc6cd78482 Merged-In: Ic0d24d36af0b87153d527083f8964ddc6cd78482 --- service/ServiceConnectivityResources/res/values/strings.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/service/ServiceConnectivityResources/res/values/strings.xml b/service/ServiceConnectivityResources/res/values/strings.xml index 7a9cf57afc..b2fa5f5b41 100644 --- a/service/ServiceConnectivityResources/res/values/strings.xml +++ b/service/ServiceConnectivityResources/res/values/strings.xml @@ -68,8 +68,6 @@ VPN - - an unknown network type From 28028e5066cd1edc530d87679a24696e86b301fe Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Mon, 15 Mar 2021 07:31:54 +0000 Subject: [PATCH 139/232] Revert "Revert "Remove connectivity dependency on Preconditions"" Preconditions.checkNotNull is deprecated to be replaced by Objects.requireNonNull, and other methods can easily be replaced by inline checks. Preconditions is an internal API class that should not be used by unbundled jars. Bug: 177046265 Change-Id: I3a67d266b32142c034520acbcdc30f7213db5e13 Merged-In: I3a67d266b32142c034520acbcdc30f7213db5e13 Test: m --- .../net/ConnectivityDiagnosticsManager.java | 5 +- .../src/android/net/ConnectivityManager.java | 48 +++++++++++-------- framework/src/android/net/MacAddress.java | 10 ++-- .../src/android/net/NetworkCapabilities.java | 11 +++-- .../android/net/StaticIpConfiguration.java | 3 +- .../src/android/net/TestNetworkManager.java | 7 ++- 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/framework/src/android/net/ConnectivityDiagnosticsManager.java b/framework/src/android/net/ConnectivityDiagnosticsManager.java index 5234494973..3598ebc701 100644 --- a/framework/src/android/net/ConnectivityDiagnosticsManager.java +++ b/framework/src/android/net/ConnectivityDiagnosticsManager.java @@ -28,7 +28,6 @@ import android.os.PersistableBundle; import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -70,8 +69,8 @@ public class ConnectivityDiagnosticsManager { /** @hide */ public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) { - mContext = Preconditions.checkNotNull(context, "missing context"); - mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + mContext = Objects.requireNonNull(context, "missing context"); + mService = Objects.requireNonNull(service, "missing IConnectivityManager"); } /** @hide */ diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index a621233e32..4cf08fcec1 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -76,7 +76,6 @@ import android.util.SparseIntArray; import com.android.connectivity.aidl.INetworkAgent; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; import libcore.net.event.NetworkEventDispatcher; @@ -1777,7 +1776,9 @@ public class ConnectivityManager { // Map from type to transports. final int NOT_FOUND = -1; final int transport = sLegacyTypeToTransport.get(type, NOT_FOUND); - Preconditions.checkArgument(transport != NOT_FOUND, "unknown legacy type: " + type); + if (transport == NOT_FOUND) { + throw new IllegalArgumentException("unknown legacy type: " + type); + } nc.addTransportType(transport); // Map from type to capabilities. @@ -1882,8 +1883,8 @@ public class ConnectivityManager { } private PacketKeepalive(Network network, PacketKeepaliveCallback callback) { - Preconditions.checkNotNull(network, "network cannot be null"); - Preconditions.checkNotNull(callback, "callback cannot be null"); + Objects.requireNonNull(network, "network cannot be null"); + Objects.requireNonNull(callback, "callback cannot be null"); mNetwork = network; mExecutor = Executors.newSingleThreadExecutor(); mCallback = new ISocketKeepaliveCallback.Stub() { @@ -2258,7 +2259,9 @@ public class ConnectivityManager { */ public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) { INetworkActivityListener rl = mNetworkActivityListeners.get(l); - Preconditions.checkArgument(rl != null, "Listener was not registered."); + if (rl == null) { + throw new IllegalArgumentException("Listener was not registered."); + } try { mService.registerNetworkActivityListener(rl); } catch (RemoteException e) { @@ -2286,8 +2289,8 @@ public class ConnectivityManager { * {@hide} */ public ConnectivityManager(Context context, IConnectivityManager service) { - mContext = Preconditions.checkNotNull(context, "missing context"); - mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + mContext = Objects.requireNonNull(context, "missing context"); + mService = Objects.requireNonNull(service, "missing IConnectivityManager"); mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE); sInstance = this; } @@ -2554,7 +2557,7 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int type, boolean showProvisioningUi, final OnStartTetheringCallback callback, Handler handler) { - Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null."); + Objects.requireNonNull(callback, "OnStartTetheringCallback cannot be null."); final Executor executor = new Executor() { @Override @@ -2647,7 +2650,7 @@ public class ConnectivityManager { public void registerTetheringEventCallback( @NonNull @CallbackExecutor Executor executor, @NonNull final OnTetheringEventCallback callback) { - Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null."); + Objects.requireNonNull(callback, "OnTetheringEventCallback cannot be null."); final TetheringEventCallback tetherCallback = new TetheringEventCallback() { @@ -2945,7 +2948,7 @@ public class ConnectivityManager { public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi, @NonNull @CallbackExecutor Executor executor, @NonNull final OnTetheringEntitlementResultListener listener) { - Preconditions.checkNotNull(listener, "TetheringEntitlementResultListener cannot be null."); + Objects.requireNonNull(listener, "TetheringEntitlementResultListener cannot be null."); ResultReceiver wrappedListener = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -3315,7 +3318,9 @@ public class ConnectivityManager { } public NetworkCallback(@Flag int flags) { - Preconditions.checkArgument((flags & VALID_FLAGS) == flags); + if ((flags & VALID_FLAGS) != flags) { + throw new IllegalArgumentException("Invalid flags"); + } mFlags = flags; } @@ -3601,7 +3606,7 @@ public class ConnectivityManager { } CallbackHandler(Handler handler) { - this(Preconditions.checkNotNull(handler, "Handler cannot be null.").getLooper()); + this(Objects.requireNonNull(handler, "Handler cannot be null.").getLooper()); } @Override @@ -3699,9 +3704,9 @@ public class ConnectivityManager { int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { printStackTrace(); checkCallbackNotNull(callback); - Preconditions.checkArgument( - reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null, - "null NetworkCapabilities"); + if (reqType != TRACK_DEFAULT && reqType != TRACK_SYSTEM_DEFAULT && need == null) { + throw new IllegalArgumentException("null NetworkCapabilities"); + } final NetworkRequest request; final String callingPackageName = mContext.getOpPackageName(); try { @@ -4048,15 +4053,17 @@ public class ConnectivityManager { } private static void checkPendingIntentNotNull(PendingIntent intent) { - Preconditions.checkNotNull(intent, "PendingIntent cannot be null."); + Objects.requireNonNull(intent, "PendingIntent cannot be null."); } private static void checkCallbackNotNull(NetworkCallback callback) { - Preconditions.checkNotNull(callback, "null NetworkCallback"); + Objects.requireNonNull(callback, "null NetworkCallback"); } private static void checkTimeout(int timeoutMs) { - Preconditions.checkArgumentPositive(timeoutMs, "timeoutMs must be strictly positive."); + if (timeoutMs <= 0) { + throw new IllegalArgumentException("timeoutMs must be strictly positive."); + } } /** @@ -4336,8 +4343,9 @@ public class ConnectivityManager { // Find all requests associated to this callback and stop callback triggers immediately. // Callback is reusable immediately. http://b/20701525, http://b/35921499. synchronized (sCallbacks) { - Preconditions.checkArgument(networkCallback.networkRequest != null, - "NetworkCallback was not registered"); + if (networkCallback.networkRequest == null) { + throw new IllegalArgumentException("NetworkCallback was not registered"); + } if (networkCallback.networkRequest == ALREADY_UNREGISTERED) { Log.d(TAG, "NetworkCallback was already unregistered"); return; diff --git a/framework/src/android/net/MacAddress.java b/framework/src/android/net/MacAddress.java index c83c23a4b6..26a504a29c 100644 --- a/framework/src/android/net/MacAddress.java +++ b/framework/src/android/net/MacAddress.java @@ -25,7 +25,6 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.util.Preconditions; import com.android.net.module.util.MacAddressUtils; import java.lang.annotation.Retention; @@ -34,6 +33,7 @@ import java.net.Inet6Address; import java.net.UnknownHostException; import java.security.SecureRandom; import java.util.Arrays; +import java.util.Objects; /** * Representation of a MAC address. @@ -229,7 +229,7 @@ public final class MacAddress implements Parcelable { * @hide */ public static @NonNull byte[] byteAddrFromStringAddr(String addr) { - Preconditions.checkNotNull(addr); + Objects.requireNonNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { throw new IllegalArgumentException(addr + " was not a valid MAC address"); @@ -275,7 +275,7 @@ public final class MacAddress implements Parcelable { // Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr)) // that avoids the allocation of an intermediary byte[]. private static long longAddrFromStringAddr(String addr) { - Preconditions.checkNotNull(addr); + Objects.requireNonNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { throw new IllegalArgumentException(addr + " was not a valid MAC address"); @@ -364,8 +364,8 @@ public final class MacAddress implements Parcelable { * */ public boolean matches(@NonNull MacAddress baseAddress, @NonNull MacAddress mask) { - Preconditions.checkNotNull(baseAddress); - Preconditions.checkNotNull(mask); + Objects.requireNonNull(baseAddress); + Objects.requireNonNull(mask); return (mAddr & mask.mAddr) == (baseAddress.mAddr & mask.mAddr); } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 058f3c999d..c6dfcee202 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -35,7 +35,6 @@ import android.util.ArraySet; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkCapabilitiesUtils; @@ -2099,8 +2098,9 @@ public final class NetworkCapabilities implements Parcelable { } private static void checkValidTransportType(@Transport int transport) { - Preconditions.checkArgument( - isValidTransport(transport), "Invalid TransportType " + transport); + if (!isValidTransport(transport)) { + throw new IllegalArgumentException("Invalid TransportType " + transport); + } } private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) { @@ -2108,8 +2108,9 @@ public final class NetworkCapabilities implements Parcelable { } private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) { - Preconditions.checkArgument(isValidCapability(capability), - "NetworkCapability " + capability + "out of range"); + if (!isValidCapability(capability)) { + throw new IllegalArgumentException("NetworkCapability " + capability + "out of range"); + } } /** diff --git a/framework/src/android/net/StaticIpConfiguration.java b/framework/src/android/net/StaticIpConfiguration.java index ce545974f5..7904f7a4ec 100644 --- a/framework/src/android/net/StaticIpConfiguration.java +++ b/framework/src/android/net/StaticIpConfiguration.java @@ -24,7 +24,6 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.util.Preconditions; import com.android.net.module.util.InetAddressUtils; import java.net.InetAddress; @@ -153,7 +152,7 @@ public final class StaticIpConfiguration implements Parcelable { * @return The {@link Builder} for chaining. */ public @NonNull Builder setDnsServers(@NonNull Iterable dnsServers) { - Preconditions.checkNotNull(dnsServers); + Objects.requireNonNull(dnsServers); mDnsServers = dnsServers; return this; } diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java index a174a7be85..a7a62351e5 100644 --- a/framework/src/android/net/TestNetworkManager.java +++ b/framework/src/android/net/TestNetworkManager.java @@ -21,10 +21,9 @@ import android.annotation.SystemApi; import android.os.IBinder; import android.os.RemoteException; -import com.android.internal.util.Preconditions; - import java.util.Arrays; import java.util.Collection; +import java.util.Objects; /** * Class that allows creation and management of per-app, test-only networks @@ -50,7 +49,7 @@ public class TestNetworkManager { /** @hide */ public TestNetworkManager(@NonNull ITestNetworkManager service) { - mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager"); + mService = Objects.requireNonNull(service, "missing ITestNetworkManager"); } /** @@ -93,7 +92,7 @@ public class TestNetworkManager { */ public void setupTestNetwork( @NonNull LinkProperties lp, boolean isMetered, @NonNull IBinder binder) { - Preconditions.checkNotNull(lp, "Invalid LinkProperties"); + Objects.requireNonNull(lp, "Invalid LinkProperties"); setupTestNetwork(lp.getInterfaceName(), lp, isMetered, new int[0], binder); } From 2125a97fb4d7e9ab48e1ec3bc97c4f3f9706e450 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 18 Mar 2021 22:08:01 +0900 Subject: [PATCH 140/232] Remove Preconditions usage in TestNetworkSpecifier Preconditions is a hidden API utility. It can be easily replace by inline checks. Bug: 177046265 Test: m Change-Id: I3f722075fb9c74e12e40348ba4faad2f0fa67178 Merged-In: I3f722075fb9c74e12e40348ba4faad2f0fa67178 --- framework/src/android/net/TestNetworkSpecifier.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/TestNetworkSpecifier.java b/framework/src/android/net/TestNetworkSpecifier.java index b7470a591d..117457dffc 100644 --- a/framework/src/android/net/TestNetworkSpecifier.java +++ b/framework/src/android/net/TestNetworkSpecifier.java @@ -23,8 +23,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import com.android.internal.util.Preconditions; - import java.util.Objects; /** @@ -43,7 +41,9 @@ public final class TestNetworkSpecifier extends NetworkSpecifier implements Parc private final String mInterfaceName; public TestNetworkSpecifier(@NonNull String interfaceName) { - Preconditions.checkStringNotEmpty(interfaceName); + if (TextUtils.isEmpty(interfaceName)) { + throw new IllegalArgumentException("Empty interfaceName"); + } mInterfaceName = interfaceName; } From b15b7dbdd4f8fac483957b7e2e9ebb00489fad02 Mon Sep 17 00:00:00 2001 From: junyulai Date: Tue, 16 Mar 2021 10:24:43 +0800 Subject: [PATCH 141/232] [VCN15] expose addUnwantedCapability and related APIs Test: m -j doc-comment-check-docs Bug: 175662146 Merged-In: I3f2e6a99e015f09cc4405f6804eac4ae33e3dcc7 Change-Id: I3f2e6a99e015f09cc4405f6804eac4ae33e3dcc7 (cherry-picked from ag/13929102) --- framework/api/module-lib-current.txt | 7 ++++ .../src/android/net/NetworkCapabilities.java | 33 +++++++++++++++---- framework/src/android/net/NetworkRequest.java | 20 +++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index aa7a0ac465..af0f50ef18 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -39,6 +39,7 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, long); method @Nullable public java.util.Set> getUids(); + method public boolean hasUnwantedCapability(int); field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L @@ -51,7 +52,13 @@ package android.net { method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set>); } + public class NetworkRequest implements android.os.Parcelable { + method public boolean hasUnwantedCapability(int); + } + public static class NetworkRequest.Builder { + method @NonNull public android.net.NetworkRequest.Builder addUnwantedCapability(int); + method @NonNull public android.net.NetworkRequest.Builder removeUnwantedCapability(int); method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set>); } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index c9c0940dfd..881fa8c270 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -639,19 +639,31 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Removes (if found) the given capability from this {@code NetworkCapability} instance. + * Removes (if found) the given capability from this {@code NetworkCapability} + * instance that were added via addCapability(int) or setCapabilities(int[], int[]). * * @param capability the capability to be removed. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) { - // Note that this method removes capabilities that were added via addCapability(int), - // addUnwantedCapability(int) or setCapabilities(int[], int[]). checkValidCapability(capability); final long mask = ~(1 << capability); mNetworkCapabilities &= mask; - mUnwantedNetworkCapabilities &= mask; + return this; + } + + /** + * Removes (if found) the given unwanted capability from this {@code NetworkCapability} + * instance that were added via addUnwantedCapability(int) or setCapabilities(int[], int[]). + * + * @param capability the capability to be removed. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities removeUnwantedCapability(@NetCapability int capability) { + checkValidCapability(capability); + mUnwantedNetworkCapabilities &= ~(1 << capability); return this; } @@ -723,6 +735,7 @@ public final class NetworkCapabilities implements Parcelable { } /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public boolean hasUnwantedCapability(@NetCapability int capability) { return isValidCapability(capability) && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0); @@ -736,10 +749,16 @@ public final class NetworkCapabilities implements Parcelable { return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0); } - /** Note this method may result in having the same capability in wanted and unwanted lists. */ private void combineNetCapabilities(@NonNull NetworkCapabilities nc) { - this.mNetworkCapabilities |= nc.mNetworkCapabilities; - this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities; + final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities; + final long unwantedCaps = + this.mUnwantedNetworkCapabilities | nc.mUnwantedNetworkCapabilities; + if ((wantedCaps & unwantedCaps) != 0) { + throw new IllegalArgumentException( + "Cannot have the same capability in wanted and unwanted lists."); + } + this.mNetworkCapabilities = wantedCaps; + this.mUnwantedNetworkCapabilities = unwantedCaps; } /** diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index cf131f0df6..23c92a52ac 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -305,11 +305,30 @@ public class NetworkRequest implements Parcelable { * * @hide */ + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addUnwantedCapability(capability); return this; } + /** + * Removes (if found) the given unwanted capability from this builder instance. + * + * @param capability The unwanted capability to remove. + * @return The builder to facilitate chaining. + * + * @hide + */ + @NonNull + @SuppressLint("BuilderSetStyle") + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public Builder removeUnwantedCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.removeUnwantedCapability(capability); + return this; + } + /** * Completely clears all the {@code NetworkCapabilities} from this builder instance, * removing even the capabilities that are set by default when the object is constructed. @@ -567,6 +586,7 @@ public class NetworkRequest implements Parcelable { * * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public boolean hasUnwantedCapability(@NetCapability int capability) { return networkCapabilities.hasUnwantedCapability(capability); } From 5f26b19afdcc95a1c9e577fea9cbc985b769b992 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Fri, 12 Mar 2021 22:48:07 +0900 Subject: [PATCH 142/232] Support calling registerDefaultNetworkCallback for another UID. This is to be used by privileged components (e.g., JobScheduler) to request callbacks about the state of other UIDs on the system. Bug: 165835257 Test: new unit test coverage Change-Id: I29f155710394e58c14fcef488db6271d8d83033a --- .../src/android/net/ConnectivityManager.java | 49 +++++++++++++++++-- .../src/android/net/IConnectivityManager.aidl | 2 +- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index a5e9f31a5b..1a5299bb00 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3695,8 +3695,9 @@ public class ConnectivityManager { private static final HashMap sCallbacks = new HashMap<>(); private static CallbackHandler sCallbackHandler; - private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, - int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + private NetworkRequest sendRequestForNetwork(int asUid, NetworkCapabilities need, + NetworkCallback callback, int timeoutMs, NetworkRequest.Type reqType, int legacyType, + CallbackHandler handler) { printStackTrace(); checkCallbackNotNull(callback); Preconditions.checkArgument( @@ -3721,8 +3722,8 @@ public class ConnectivityManager { getAttributionTag()); } else { request = mService.requestNetwork( - need, reqType.ordinal(), messenger, timeoutMs, binder, legacyType, - callbackFlags, callingPackageName, getAttributionTag()); + asUid, need, reqType.ordinal(), messenger, timeoutMs, binder, + legacyType, callbackFlags, callingPackageName, getAttributionTag()); } if (request != null) { sCallbacks.put(request, callback); @@ -3737,6 +3738,12 @@ public class ConnectivityManager { return request; } + private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback, + int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) { + return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType, + legacyType, handler); + } + /** * Helper function to request a network with a particular legacy type. * @@ -4220,8 +4227,40 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, @NonNull Handler handler) { + registerDefaultNetworkCallbackAsUid(Process.INVALID_UID, networkCallback, handler); + } + + /** + * Registers to receive notifications about changes in the default network for the specified + * UID. This may be a physical network or a virtual network, such as a VPN that applies to the + * UID. The callbacks will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + *

    To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param uid the UID for which to track default network changes. + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * UID's default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + * @hide + */ + // TODO: @SystemApi(client=MODULE_LIBRARIES) + @SuppressLint({"ExecutorRegistration", "PairedRegistration"}) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void registerDefaultNetworkCallbackAsUid(int uid, + @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { CallbackHandler cbHandler = new CallbackHandler(handler); - sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, + sendRequestForNetwork(uid, null /* need */, networkCallback, 0 /* timeoutMs */, TRACK_DEFAULT, TYPE_NONE, cbHandler); } diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 3300fa8fd1..0826922e21 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -142,7 +142,7 @@ interface IConnectivityManager in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config, in int factorySerialNumber); - NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities, int reqType, + NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType, in Messenger messenger, int timeoutSec, in IBinder binder, int legacy, int callbackFlags, String callingPackageName, String callingAttributionTag); From b90bdbdbf410fb7f01147d517f8add27e7b60629 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 22 Mar 2021 18:23:21 +0900 Subject: [PATCH 143/232] Expose registerDefaultNetworkCallbackAsUid. Bug: 165835257 Test: atest FrameworksNetTests Change-Id: I638ed5cd5273d456919630aba1e22f099df1b36c --- framework/api/module-lib-current.txt | 1 + framework/src/android/net/ConnectivityManager.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index aa7a0ac465..f32f08daf7 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -11,6 +11,7 @@ package android.net { method @Nullable public android.net.ProxyInfo getGlobalProxy(); method @NonNull public static android.util.Range getIpSecNetIdRange(); method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 1a5299bb00..2e240f947d 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4252,7 +4252,7 @@ public class ConnectivityManager { * @throws RuntimeException if the app already has too many callbacks registered. * @hide */ - // TODO: @SystemApi(client=MODULE_LIBRARIES) + @SystemApi(client = MODULE_LIBRARIES) @SuppressLint({"ExecutorRegistration", "PairedRegistration"}) @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, From 0e345e9b1a3b6115f24cdb5ed0ac54260d1e84a8 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Mon, 22 Mar 2021 15:07:41 +0800 Subject: [PATCH 144/232] Have a new method in NetworkAgentConfig.Builder to set allowBypass Have a new method in NetworkAgentConfig.Builder for Vpn to set allowBypass. Bug: 182963397 Test: m Change-Id: I3f244464438325ee7f8a1b953d3fb28186293628 --- framework/api/module-lib-current.txt | 2 ++ .../src/android/net/NetworkAgentConfig.java | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index f32f08daf7..23a71798d7 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -31,9 +31,11 @@ package android.net { public final class NetworkAgentConfig implements android.os.Parcelable { method @Nullable public String getSubscriberId(); + method public boolean isBypassableVpn(); } public static final class NetworkAgentConfig.Builder { + method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); } diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java index 5e50a6404a..0bd2371bfc 100644 --- a/framework/src/android/net/NetworkAgentConfig.java +++ b/framework/src/android/net/NetworkAgentConfig.java @@ -63,6 +63,16 @@ public final class NetworkAgentConfig implements Parcelable { return explicitlySelected; } + /** + * @return whether this VPN connection can be bypassed by the apps. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public boolean isBypassableVpn() { + return allowBypass; + } + /** * Set if the user desires to use this network even if it is unvalidated. This field has meaning * only if {@link explicitlySelected} is true. If it is, this field must also be set to the @@ -346,6 +356,19 @@ public final class NetworkAgentConfig implements Parcelable { return this; } + /** + * Sets whether the apps can bypass the VPN connection. + * + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + @SystemApi(client = MODULE_LIBRARIES) + public Builder setBypassableVpn(boolean allowBypass) { + mConfig.allowBypass = allowBypass; + return this; + } + /** * Returns the constructed {@link NetworkAgentConfig} object. */ From e2179dd1ccd919a32a870842d39a3c504368a6ec Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Mon, 22 Mar 2021 17:24:11 +0800 Subject: [PATCH 145/232] Add NetworkRequest.Builder creating from an existing instance Provide a formal way to construct a new NetworkRequest from an existing instance. The network capabilities inside the NetworkRequest is hidden. There is no way to pass a NetworkRequest and update its capabilities. Add NetworkRequest.Builder creating from an existing instance to allow to clone the network capabilities. Bug: 172183305 Test: make update-api Change-Id: I068462b2a1410daf67b0c95f2b643d396f079531 Merged-In: I068462b2a1410daf67b0c95f2b643d396f079531 --- framework/api/current.txt | 1 + framework/src/android/net/NetworkRequest.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/framework/api/current.txt b/framework/api/current.txt index e415e01fea..ad44b27f6d 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -396,6 +396,7 @@ package android.net { public static class NetworkRequest.Builder { ctor public NetworkRequest.Builder(); + ctor public NetworkRequest.Builder(@NonNull android.net.NetworkRequest); method public android.net.NetworkRequest.Builder addCapability(int); method public android.net.NetworkRequest.Builder addTransportType(int); method public android.net.NetworkRequest build(); diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index cf131f0df6..f9b3db12c0 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -215,6 +215,14 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setSingleUid(Process.myUid()); } + /** + * Creates a new Builder of NetworkRequest from an existing instance. + */ + public Builder(@NonNull final NetworkRequest request) { + Objects.requireNonNull(request); + mNetworkCapabilities = request.networkCapabilities; + } + /** * Build {@link NetworkRequest} give the current set of capabilities. */ From d57b2853a3818f97967d91a831eb165e3ede483c Mon Sep 17 00:00:00 2001 From: Jayachandran C Date: Mon, 15 Mar 2021 15:58:11 -0700 Subject: [PATCH 146/232] Add 5G/NR QOS support Bug: 155176305 Bug: 182317794 Test: atest ConnectivityServiceTest atest QosCallbackTrackerTest Change-Id: Idf6d8a7c3b80bc50a2c1244ceaefea9381d40c2f --- framework/api/system-current.txt | 5 +-- .../android/net/INetworkAgentRegistry.aidl | 2 ++ framework/src/android/net/IQosCallback.aidl | 3 ++ framework/src/android/net/NetworkAgent.java | 31 ++++++++++++------- .../android/net/QosCallbackConnection.java | 20 ++++++++++++ framework/src/android/net/QosSession.java | 6 ++++ 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 703fca408f..7733221d52 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -230,8 +230,8 @@ package android.net { method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); method public final void sendNetworkScore(@IntRange(from=0, to=99) int); method public final void sendQosCallbackError(int, int); - method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); - method public final void sendQosSessionLost(int, int); + method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes); + method public final void sendQosSessionLost(int, int, int); method public final void sendSocketKeepaliveEvent(int, int); method public final void setUnderlyingNetworks(@Nullable java.util.List); method public void unregister(); @@ -363,6 +363,7 @@ package android.net { method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int TYPE_EPS_BEARER = 1; // 0x1 + field public static final int TYPE_NR_BEARER = 2; // 0x2 } public interface QosSessionAttributes { diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl index c5464d3241..cbd6193744 100644 --- a/framework/src/android/net/INetworkAgentRegistry.aidl +++ b/framework/src/android/net/INetworkAgentRegistry.aidl @@ -22,6 +22,7 @@ import android.net.NetworkInfo; import android.net.NetworkScore; import android.net.QosSession; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; /** * Interface for NetworkAgents to send network properties. @@ -37,6 +38,7 @@ oneway interface INetworkAgentRegistry { void sendSocketKeepaliveEvent(int slot, int reason); void sendUnderlyingNetworks(in @nullable List networks); void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes); + void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes); void sendQosSessionLost(int qosCallbackId, in QosSession session); void sendQosCallbackError(int qosCallbackId, int exceptionType); } diff --git a/framework/src/android/net/IQosCallback.aidl b/framework/src/android/net/IQosCallback.aidl index 91c75759f8..c9735419f7 100644 --- a/framework/src/android/net/IQosCallback.aidl +++ b/framework/src/android/net/IQosCallback.aidl @@ -19,6 +19,7 @@ package android.net; import android.os.Bundle; import android.net.QosSession; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; /** * AIDL interface for QosCallback @@ -29,6 +30,8 @@ oneway interface IQosCallback { void onQosEpsBearerSessionAvailable(in QosSession session, in EpsBearerQosSessionAttributes attributes); + void onNrQosSessionAvailable(in QosSession session, + in NrQosSessionAttributes attributes); void onQosSessionLost(in QosSession session); void onError(in int type); } diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index 3863ed1113..2eefba9eaf 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -32,6 +32,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -1160,29 +1161,37 @@ public abstract class NetworkAgent { /** - * Sends the attributes of Eps Bearer Qos Session back to the Application + * Sends the attributes of Qos Session back to the Application * * @param qosCallbackId the callback id that the session belongs to - * @param sessionId the unique session id across all Eps Bearer Qos Sessions - * @param attributes the attributes of the Eps Qos Session + * @param sessionId the unique session id across all Qos Sessions + * @param attributes the attributes of the Qos Session */ public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId, - @NonNull final EpsBearerQosSessionAttributes attributes) { + @NonNull final QosSessionAttributes attributes) { Objects.requireNonNull(attributes, "The attributes must be non-null"); - queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, - new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), - attributes)); + if (attributes instanceof EpsBearerQosSessionAttributes) { + queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), + (EpsBearerQosSessionAttributes)attributes)); + } else if (attributes instanceof NrQosSessionAttributes) { + queueOrSendMessage(ra -> ra.sendNrQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_NR_BEARER), + (NrQosSessionAttributes)attributes)); + } } /** - * Sends event that the Eps Qos Session was lost. + * Sends event that the Qos Session was lost. * * @param qosCallbackId the callback id that the session belongs to - * @param sessionId the unique session id across all Eps Bearer Qos Sessions + * @param sessionId the unique session id across all Qos Sessions + * @param qosSessionType the session type {@code QosSesson#QosSessionType} */ - public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) { + public final void sendQosSessionLost(final int qosCallbackId, + final int sessionId, final int qosSessionType) { queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId, - new QosSession(sessionId, QosSession.TYPE_EPS_BEARER))); + new QosSession(sessionId, qosSessionType))); } /** diff --git a/framework/src/android/net/QosCallbackConnection.java b/framework/src/android/net/QosCallbackConnection.java index bdb4ad68cd..de0fc243b4 100644 --- a/framework/src/android/net/QosCallbackConnection.java +++ b/framework/src/android/net/QosCallbackConnection.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.NonNull; import android.annotation.Nullable; import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; import com.android.internal.annotations.VisibleForTesting; @@ -83,6 +84,25 @@ class QosCallbackConnection extends android.net.IQosCallback.Stub { }); } + /** + * Called when either the {@link NrQosSessionAttributes} has changed or on the first time + * the attributes have become available. + * + * @param session the session that is now available + * @param attributes the corresponding attributes of session + */ + @Override + public void onNrQosSessionAvailable(@NonNull final QosSession session, + @NonNull final NrQosSessionAttributes attributes) { + + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionAvailable(session, attributes); + } + }); + } + /** * Called when the session is lost. * diff --git a/framework/src/android/net/QosSession.java b/framework/src/android/net/QosSession.java index 4f3bb77c58..93f2ff2bf7 100644 --- a/framework/src/android/net/QosSession.java +++ b/framework/src/android/net/QosSession.java @@ -36,6 +36,11 @@ public final class QosSession implements Parcelable { */ public static final int TYPE_EPS_BEARER = 1; + /** + * The {@link QosSession} is a NR Session. + */ + public static final int TYPE_NR_BEARER = 2; + private final int mSessionId; private final int mSessionType; @@ -100,6 +105,7 @@ public final class QosSession implements Parcelable { */ @IntDef(value = { TYPE_EPS_BEARER, + TYPE_NR_BEARER, }) @interface QosSessionType {} From f1520bb8dc5cd68df074844392e0aa82d7873885 Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Tue, 23 Mar 2021 08:12:28 +0000 Subject: [PATCH 147/232] Move BLOCKED_REASON_* constants from NPMS into ConnectivityManager. These constants will now be including all the reasons for why an uid's network access can be blocked, instead of only the restrictions that could be imposed by NPMS. Bug: 183473548 Test: atest ./tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java Merged-In: I4c544415e12adf442fd2415c371b1b70a39c3aa4 Change-Id: I6dcea43fbefa9eac8b5a971b822a5be5422a54b4 --- framework/api/module-lib-current.txt | 8 ++ .../src/android/net/ConnectivityManager.java | 88 +++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 1bb6a127ca..6d21e4fc36 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -22,6 +22,14 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network); method public void systemReady(); + field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000 + field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000 + field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000 + field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4 + field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1 + field public static final int BLOCKED_REASON_DOZE = 2; // 0x2 + field public static final int BLOCKED_REASON_NONE = 0; // 0x0 + field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8 field public static final String PRIVATE_DNS_MODE_OFF = "off"; field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index b3e2286754..0a4d409c7d 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -826,6 +826,94 @@ public class ConnectivityManager { }) public @interface PrivateDnsMode {} + /** + * Flag to indicate that an app is not subject to any restrictions that could result in its + * network access blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_NONE = 0; + + /** + * Flag to indicate that an app is subject to Battery saver restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0; + + /** + * Flag to indicate that an app is subject to Doze restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_DOZE = 1 << 1; + + /** + * Flag to indicate that an app is subject to App Standby restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2; + + /** + * Flag to indicate that an app is subject to Restricted mode restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3; + + /** + * Flag to indicate that an app is subject to Data saver restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16; + + /** + * Flag to indicate that an app is subject to user restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17; + + /** + * Flag to indicate that an app is subject to Device admin restrictions that would + * result in its metered network access being blocked. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"BLOCKED_"}, value = { + BLOCKED_REASON_NONE, + BLOCKED_REASON_BATTERY_SAVER, + BLOCKED_REASON_DOZE, + BLOCKED_REASON_APP_STANDBY, + BLOCKED_REASON_RESTRICTED_MODE, + BLOCKED_METERED_REASON_DATA_SAVER, + BLOCKED_METERED_REASON_USER_RESTRICTED, + BLOCKED_METERED_REASON_ADMIN_DISABLED, + }) + public @interface BlockedReason {} + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; From 29278eef92b8935479cfa9e870828f7825ea9969 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Mon, 22 Mar 2021 11:51:27 +0800 Subject: [PATCH 148/232] Expose some APIs from ConnectivityManager - Expose setRequireVpnForUids to Vpn.java - Expose setLegacyLockdownVpnEnabled to LockdownVpnTracker.java - Expose requestRouteToHostAddress to GnssNetworkConnectivityHandler.java Bug: 182963397 Test: m Change-Id: I1fb5ecfbe37878ba3534e6c6c7599ca29db2735c Merged-In: I1fb5ecfbe37878ba3534e6c6c7599ca29db2735c (cherry-picked from ag/13927657) --- framework/api/module-lib-current.txt | 3 +++ framework/src/android/net/ConnectivityManager.java | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 1bb6a127ca..112d9f0a58 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -14,11 +14,14 @@ package android.net { method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection>); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network); method public void systemReady(); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index b3e2286754..de85833c56 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1122,12 +1122,13 @@ public class ConnectivityManager { * @param ranges the UID ranges to restrict * @param requireVpn whether the specified UID ranges must use a VPN * - * TODO: expose as @SystemApi. * @hide */ @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_STACK}) + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) public void setRequireVpnForUids(boolean requireVpn, @NonNull Collection> ranges) { Objects.requireNonNull(ranges); @@ -1171,13 +1172,13 @@ public class ConnectivityManager { * * @param enabled whether legacy lockdown VPN is enabled or disabled * - * TODO: @SystemApi(client = MODULE_LIBRARIES) - * * @hide */ @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) public void setLegacyLockdownVpnEnabled(boolean enabled) { try { mService.setLegacyLockdownVpnEnabled(enabled); @@ -2124,6 +2125,7 @@ public class ConnectivityManager { */ @Deprecated @UnsupportedAppUsage + @SystemApi(client = MODULE_LIBRARIES) public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { checkLegacyRoutingApiAccess(); try { From 39907ba9d665f8cab71e75765b89fe8475f140f3 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Tue, 23 Mar 2021 16:17:37 +0800 Subject: [PATCH 149/232] Expose NetworkScore to external caller NetworkScore will be a part of mainline module, so the external callers cannot call its hidden APIs. Expose needed APIs to the external callers. CTS-Coverage-Bug: 182963397 Bug: 182963397 Test: m Change-Id: Iddf8c71a5f51a40bc6ff78626b3e8ee530d1b7eb Merged-In: Iddf8c71a5f51a40bc6ff78626b3e8ee530d1b7eb (cherry-picked from ag/13947595) --- framework/api/system-current.txt | 13 +++++++++++++ framework/src/android/net/NetworkScore.java | 5 ++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 703fca408f..8c1fae91eb 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -316,6 +316,19 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); } + public final class NetworkScore implements android.os.Parcelable { + method public int describeContents(); + method public int getLegacyInt(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class NetworkScore.Builder { + ctor public NetworkScore.Builder(); + method @NonNull public android.net.NetworkScore build(); + method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int); + } + public final class OemNetworkPreferences implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Map getNetworkPreferences(); diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java index eadcb2d0a7..65849930fa 100644 --- a/framework/src/android/net/NetworkScore.java +++ b/framework/src/android/net/NetworkScore.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -29,7 +30,7 @@ import com.android.internal.annotations.VisibleForTesting; * network is considered for a particular use. * @hide */ -// TODO : @SystemApi when the implementation is complete +@SystemApi public final class NetworkScore implements Parcelable { // This will be removed soon. Do *NOT* depend on it for any new code that is not part of // a migration. @@ -62,6 +63,8 @@ public final class NetworkScore implements Parcelable { /** * @return whether this score has a particular policy. + * + * @hide */ @VisibleForTesting public boolean hasPolicy(final int policy) { From 4694a2ab0437f61f46c295416470c46d29286a98 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Tue, 23 Mar 2021 16:12:44 +0800 Subject: [PATCH 150/232] Expose NetworkAgent constructor taking a NetworkScore parameter Vpn constructs NetworkAgent with this exposed NetworkAgent constructor. Given NetworkAgent is moving into the incoming connectivity module, Vpn which is outside the module will not be able to access it if it's not in the API surface. Thus, expose it to allow Vpn to use it. Bug: 182963397 Test: make update-api Change-Id: Ic2357dcfff3233e0dd17b48b0e376a5095ef60fa Merged-In: Ic2357dcfff3233e0dd17b48b0e376a5095ef60fa CTS-Coverage-Bug: 172183305 (cherry-picked from ag/13966707) --- framework/api/system-current.txt | 1 + framework/src/android/net/NetworkAgent.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 8c1fae91eb..11aa7063da 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -212,6 +212,7 @@ package android.net { public abstract class NetworkAgent { ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); + ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); method @Nullable public android.net.Network getNetwork(); method public void markConnected(); method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index 3863ed1113..05f8460770 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -390,7 +390,6 @@ public abstract class NetworkAgent { * @param score the initial score of this network. Update with sendNetworkScore. * @param config an immutable {@link NetworkAgentConfig} for this agent. * @param provider the {@link NetworkProvider} managing this agent. - * @hide TODO : unhide when impl is complete */ public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, From e211de289350068bcdf362acafdd6d9739567dee Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Wed, 24 Mar 2021 18:39:17 +0800 Subject: [PATCH 151/232] Add network created callback support Create a network callback to notify network agent after netd has created the network, so that the NetworkAgent may wait for this callback and ensure the pre-work to communicate with netd completed. Bug: 178725261 Test: make update-api Change-Id: I8c2ff7bd6980ae838abc5669f9419d62741b8666 Merged-In: I8c2ff7bd6980ae838abc5669f9419d62741b8666 --- framework/api/system-current.txt | 1 + framework/src/android/net/INetworkAgent.aidl | 1 + framework/src/android/net/NetworkAgent.java | 22 ++++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 7733221d52..301387f5bd 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -216,6 +216,7 @@ package android.net { method public void markConnected(); method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); method public void onAutomaticReconnectDisabled(); + method public void onNetworkCreated(); method public void onNetworkUnwanted(); method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); method public void onQosCallbackUnregistered(int); diff --git a/framework/src/android/net/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl index 1f66e18717..078acbd8fe 100644 --- a/framework/src/android/net/INetworkAgent.aidl +++ b/framework/src/android/net/INetworkAgent.aidl @@ -46,4 +46,5 @@ oneway interface INetworkAgent { void onRemoveKeepalivePacketFilter(int slot); void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel); void onQosCallbackUnregistered(int qosCallbackId); + void onNetworkCreated(); } diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index 2eefba9eaf..aea70e3168 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -362,6 +362,14 @@ public abstract class NetworkAgent { */ public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21; + /** + * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native + * network was created and the Network object is now valid. + * + * @hide + */ + public static final int CMD_NETWORK_CREATED = BASE + 22; + private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { // The subtype can be changed with (TODO) setLegacySubtype, but it starts // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description. @@ -563,6 +571,10 @@ public abstract class NetworkAgent { msg.arg1 /* QoS callback id */); break; } + case CMD_NETWORK_CREATED: { + onNetworkCreated(); + break; + } } } } @@ -703,6 +715,11 @@ public abstract class NetworkAgent { mHandler.sendMessage(mHandler.obtainMessage( CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null)); } + + @Override + public void onNetworkCreated() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_CREATED)); + } } /** @@ -1010,6 +1027,11 @@ public abstract class NetworkAgent { protected void saveAcceptUnvalidated(boolean accept) { } + /** + * Called when ConnectivityService has successfully created this NetworkAgent's native network. + */ + public void onNetworkCreated() {} + /** * Requests that the network hardware send the specified packet at the specified interval. * From 944309551787611d13388a6659e2e0523f974f56 Mon Sep 17 00:00:00 2001 From: paulhu Date: Tue, 23 Mar 2021 00:24:50 +0800 Subject: [PATCH 152/232] Expose ConnectivitySettingsManager as module-lib API Have getter/setter methods for external modules that can get/set the setting values. Bug: 182538166 Test: atest FrameworksNetTests Merged-In: I82225a43f95e3e1d3e52c4e7a0fc541c0087292e Change-Id: I61cb00216494e35b6e0dfe444b177cad36ad8afb --- framework/api/module-lib-current.txt | 44 ++ .../net/ConnectivitySettingsManager.java | 657 +++++++++++++++++- 2 files changed, 700 insertions(+), 1 deletion(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index ac69a3e795..3a4cebc93a 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -40,6 +40,50 @@ package android.net { field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 } + public class ConnectivitySettingsManager { + method public static void clearGlobalProxy(@NonNull android.content.Context); + method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context); + method public static int getCaptivePortalMode(@NonNull android.content.Context, int); + method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method @NonNull public static android.util.Range getDnsResolverSampleRanges(@NonNull android.content.Context); + method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int); + method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context); + method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean); + method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context); + method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context); + method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int); + method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context); + method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context); + method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean); + method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String); + method public static void setCaptivePortalMode(@NonNull android.content.Context, int); + method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setDnsResolverSampleRanges(@NonNull android.content.Context, @NonNull android.util.Range); + method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int); + method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo); + method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean); + method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int); + method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String); + method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int); + method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); + method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull String); + method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String); + method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean); + method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); + field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2 + field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0 + field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1 + field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2 + field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0 + field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1 + } + public final class NetworkAgentConfig implements android.os.Parcelable { method @Nullable public String getSubscriberId(); method public boolean isBypassableVpn(); diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index bbd83931ee..e133d5e53f 100644 --- a/framework/src/android/net/ConnectivitySettingsManager.java +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -16,16 +16,38 @@ package android.net; +import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER; +import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE; +import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.net.ConnectivityManager.MultipathPreference; +import android.net.ConnectivityManager.PrivateDnsMode; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Range; + +import com.android.net.module.util.ProxyUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.Duration; +import java.util.List; /** * A manager class for connectivity module settings. * * @hide */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public class ConnectivitySettingsManager { private ConnectivitySettingsManager() {} @@ -45,12 +67,16 @@ public class ConnectivitySettingsManager { * Network activity refers to transmitting or receiving data on the network interfaces. * * Tracking is disabled if set to zero or negative value. + * + * @hide */ public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile"; /** * Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE} * but for Wifi network. + * + * @hide */ public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi"; @@ -58,12 +84,16 @@ public class ConnectivitySettingsManager { /** * Sample validity in seconds to configure for the system DNS resolver. + * + * @hide */ public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS = "dns_resolver_sample_validity_seconds"; /** * Success threshold in percent for use with the system DNS resolver. + * + * @hide */ public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT = "dns_resolver_success_threshold_percent"; @@ -71,24 +101,35 @@ public class ConnectivitySettingsManager { /** * Minimum number of samples needed for statistics to be considered meaningful in the * system DNS resolver. + * + * @hide */ public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples"; /** * Maximum number taken into account for statistics purposes in the system DNS resolver. + * + * @hide */ public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples"; + private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8; + private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64; + /** Network switch notification settings */ /** * The maximum number of notifications shown in 24 hours when switching networks. + * + * @hide */ public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT = "network_switch_notification_daily_limit"; /** * The minimum time in milliseconds between notifications when switching networks. + * + * @hide */ public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS = "network_switch_notification_rate_limit_millis"; @@ -98,14 +139,18 @@ public class ConnectivitySettingsManager { /** * The URL used for HTTP captive portal detection upon a new connection. * A 204 response code from the server is used for validation. + * + * @hide */ public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url"; /** * What to do when connecting a network that presents a captive portal. - * Must be one of the CAPTIVE_PORTAL_MODE_* constants above. + * Must be one of the CAPTIVE_PORTAL_MODE_* constants below. * * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT. + * + * @hide */ public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode"; @@ -139,11 +184,15 @@ public class ConnectivitySettingsManager { /** * Host name for global http proxy. Set via ConnectivityManager. + * + * @hide */ public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host"; /** * Integer host port for global http proxy. Set via ConnectivityManager. + * + * @hide */ public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port"; @@ -153,12 +202,16 @@ public class ConnectivitySettingsManager { * Domains should be listed in a comma- separated list. Example of * acceptable formats: ".domain1.com,my.domain2.com" Use * ConnectivityManager to set/get. + * + * @hide */ public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST = "global_http_proxy_exclusion_list"; /** * The location PAC File for the proxy. + * + * @hide */ public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url"; @@ -171,11 +224,15 @@ public class ConnectivitySettingsManager { * a specific provider. It may be used to store the provider name even when the * mode changes so that temporarily disabling and re-enabling the specific * provider mode does not necessitate retyping the provider hostname. + * + * @hide */ public static final String PRIVATE_DNS_MODE = "private_dns_mode"; /** * The specific Private DNS provider name. + * + * @hide */ public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier"; @@ -185,6 +242,8 @@ public class ConnectivitySettingsManager { * all of which require explicit user action to enable/configure. See also b/79719289. * * Value is a string, suitable for assignment to PRIVATE_DNS_MODE above. + * + * @hide */ public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode"; @@ -194,6 +253,8 @@ public class ConnectivitySettingsManager { * The number of milliseconds to hold on to a PendingIntent based request. This delay gives * the receivers of the PendingIntent an opportunity to make a new network request before * the Network satisfying the request is potentially removed. + * + * @hide */ public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS = "connectivity_release_pending_intent_delay_ms"; @@ -205,6 +266,8 @@ public class ConnectivitySettingsManager { * See ConnectivityService for more info. * * (0 = disabled, 1 = enabled) + * + * @hide */ public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; @@ -217,6 +280,8 @@ public class ConnectivitySettingsManager { * See ConnectivityService for more info. * * (0 = disabled, 1 = enabled) + * + * @hide */ public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested"; @@ -228,14 +293,604 @@ public class ConnectivitySettingsManager { * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013. * null: Ask the user whether to switch away from bad wifi. * 1: Avoid bad wifi. + * + * @hide */ public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi"; + /** + * Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013. + */ + public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; + + /** + * Ask the user whether to switch away from bad wifi. + */ + public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; + + /** + * Avoid bad wifi. + */ + public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + NETWORK_AVOID_BAD_WIFI_IGNORE, + NETWORK_AVOID_BAD_WIFI_PROMPT, + NETWORK_AVOID_BAD_WIFI_AVOID, + }) + public @interface NetworkAvoidBadWifi {} + /** * User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be * overridden by the system based on device or application state. If null, the value * specified by config_networkMeteredMultipathPreference is used. + * + * @hide */ public static final String NETWORK_METERED_MULTIPATH_PREFERENCE = "network_metered_multipath_preference"; + + /** + * Get mobile data activity timeout from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @param def The default timeout if no setting value. + * @return The {@link Duration} of timeout to track mobile data activity. + */ + @NonNull + public static Duration getMobileDataActivityTimeout(@NonNull Context context, + @NonNull Duration def) { + final int timeout = Settings.Global.getInt( + context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE, (int) def.getSeconds()); + return Duration.ofSeconds(timeout); + } + + /** + * Set mobile data activity timeout to {@link Settings}. + * Tracking is disabled if set to zero or negative value. + * + * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be + * ignored. + * + * @param context The {@link Context} to set the setting. + * @param timeout The mobile data activity timeout. + */ + public static void setMobileDataActivityTimeout(@NonNull Context context, + @NonNull Duration timeout) { + Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE, + (int) timeout.getSeconds()); + } + + /** + * Get wifi data activity timeout from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @param def The default timeout if no setting value. + * @return The {@link Duration} of timeout to track wifi data activity. + */ + @NonNull + public static Duration getWifiDataActivityTimeout(@NonNull Context context, + @NonNull Duration def) { + final int timeout = Settings.Global.getInt( + context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI, (int) def.getSeconds()); + return Duration.ofSeconds(timeout); + } + + /** + * Set wifi data activity timeout to {@link Settings}. + * Tracking is disabled if set to zero or negative value. + * + * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be + * ignored. + * + * @param context The {@link Context} to set the setting. + * @param timeout The wifi data activity timeout. + */ + public static void setWifiDataActivityTimeout(@NonNull Context context, + @NonNull Duration timeout) { + Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI, + (int) timeout.getSeconds()); + } + + /** + * Get dns resolver sample validity duration from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @param def The default duration if no setting value. + * @return The {@link Duration} of sample validity duration to configure for the system DNS + * resolver. + */ + @NonNull + public static Duration getDnsResolverSampleValidityDuration(@NonNull Context context, + @NonNull Duration def) { + final int duration = Settings.Global.getInt(context.getContentResolver(), + DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, (int) def.getSeconds()); + return Duration.ofSeconds(duration); + } + + /** + * Set dns resolver sample validity duration to {@link Settings}. The duration must be a + * positive number of seconds. + * + * @param context The {@link Context} to set the setting. + * @param duration The sample validity duration. + */ + public static void setDnsResolverSampleValidityDuration(@NonNull Context context, + @NonNull Duration duration) { + final int time = (int) duration.getSeconds(); + if (time <= 0) { + throw new IllegalArgumentException("Invalid duration"); + } + Settings.Global.putInt( + context.getContentResolver(), DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, time); + } + + /** + * Get dns resolver success threshold percent from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @param def The default value if no setting value. + * @return The success threshold in percent for use with the system DNS resolver. + */ + public static int getDnsResolverSuccessThresholdPercent(@NonNull Context context, int def) { + return Settings.Global.getInt( + context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, def); + } + + /** + * Set dns resolver success threshold percent to {@link Settings}. The threshold percent must + * be 0~100. + * + * @param context The {@link Context} to set the setting. + * @param percent The success threshold percent. + */ + public static void setDnsResolverSuccessThresholdPercent(@NonNull Context context, + @IntRange(from = 0, to = 100) int percent) { + if (percent < 0 || percent > 100) { + throw new IllegalArgumentException("Percent must be 0~100"); + } + Settings.Global.putInt( + context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, percent); + } + + /** + * Get dns resolver samples range from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The {@link Range} of samples needed for statistics to be considered + * meaningful in the system DNS resolver. + */ + @NonNull + public static Range getDnsResolverSampleRanges(@NonNull Context context) { + final int minSamples = Settings.Global.getInt(context.getContentResolver(), + DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES); + final int maxSamples = Settings.Global.getInt(context.getContentResolver(), + DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES); + return new Range<>(minSamples, maxSamples); + } + + /** + * Set dns resolver samples range to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param range The samples range. The minimum number should be more than 0 and the maximum + * number should be less that 64. + */ + public static void setDnsResolverSampleRanges(@NonNull Context context, + @NonNull Range range) { + if (range.getLower() < 0 || range.getUpper() > 64) { + throw new IllegalArgumentException("Argument must be 0~64"); + } + Settings.Global.putInt( + context.getContentResolver(), DNS_RESOLVER_MIN_SAMPLES, range.getLower()); + Settings.Global.putInt( + context.getContentResolver(), DNS_RESOLVER_MAX_SAMPLES, range.getUpper()); + } + + /** + * Get maximum count (from {@link Settings}) of switching network notifications shown in 24 + * hours. + * + * @param context The {@link Context} to query the setting. + * @param def The default value if no setting value. + * @return The maximum count of notifications shown in 24 hours when switching networks. + */ + public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context, + int def) { + return Settings.Global.getInt( + context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, def); + } + + /** + * Set maximum count (to {@link Settings}) of switching network notifications shown in 24 hours. + * The count must be at least 0. + * + * @param context The {@link Context} to set the setting. + * @param count The maximum count of switching network notifications shown in 24 hours. + */ + public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context, + @IntRange(from = 0) int count) { + if (count < 0) { + throw new IllegalArgumentException("Count must be 0~10."); + } + Settings.Global.putInt( + context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, count); + } + + /** + * Get minimum duration (from {@link Settings}) between each switching network notifications. + * + * @param context The {@link Context} to query the setting. + * @param def The default time if no setting value. + * @return The minimum duration between notifications when switching networks. + */ + @NonNull + public static Duration getNetworkSwitchNotificationRateDuration(@NonNull Context context, + @NonNull Duration def) { + final int duration = Settings.Global.getInt(context.getContentResolver(), + NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, (int) def.toMillis()); + return Duration.ofMillis(duration); + } + + /** + * Set minimum duration (to {@link Settings}) between each switching network notifications. + * + * @param context The {@link Context} to set the setting. + * @param duration The minimum duration between notifications when switching networks. + */ + public static void setNetworkSwitchNotificationRateDuration(@NonNull Context context, + @NonNull Duration duration) { + final int time = (int) duration.toMillis(); + if (time < 0) { + throw new IllegalArgumentException("Invalid duration."); + } + Settings.Global.putInt(context.getContentResolver(), + NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, time); + } + + /** + * Get URL (from {@link Settings}) used for HTTP captive portal detection upon a new connection. + * + * @param context The {@link Context} to query the setting. + * @return The URL used for HTTP captive portal detection upon a new connection. + */ + @Nullable + public static String getCaptivePortalHttpUrl(@NonNull Context context) { + return Settings.Global.getString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL); + } + + /** + * Set URL (to {@link Settings}) used for HTTP captive portal detection upon a new connection. + * This URL should respond with a 204 response to a GET request to indicate no captive portal is + * present. And this URL must be HTTP as redirect responses are used to find captive portal + * sign-in pages. If the URL set to null or be incorrect, it will result in captive portal + * detection failed and lost the connection. + * + * @param context The {@link Context} to set the setting. + * @param url The URL used for HTTP captive portal detection upon a new connection. + */ + public static void setCaptivePortalHttpUrl(@NonNull Context context, @Nullable String url) { + Settings.Global.putString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL, url); + } + + /** + * Get mode (from {@link Settings}) when connecting a network that presents a captive portal. + * + * @param context The {@link Context} to query the setting. + * @param def The default mode if no setting value. + * @return The mode when connecting a network that presents a captive portal. + */ + @CaptivePortalMode + public static int getCaptivePortalMode(@NonNull Context context, + @CaptivePortalMode int def) { + return Settings.Global.getInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, def); + } + + /** + * Set mode (to {@link Settings}) when connecting a network that presents a captive portal. + * + * @param context The {@link Context} to set the setting. + * @param mode The mode when connecting a network that presents a captive portal. + */ + public static void setCaptivePortalMode(@NonNull Context context, @CaptivePortalMode int mode) { + if (!(mode == CAPTIVE_PORTAL_MODE_IGNORE + || mode == CAPTIVE_PORTAL_MODE_PROMPT + || mode == CAPTIVE_PORTAL_MODE_AVOID)) { + throw new IllegalArgumentException("Invalid captive portal mode"); + } + Settings.Global.putInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, mode); + } + + /** + * Get the global HTTP proxy applied to the device, or null if none. + * + * @param context The {@link Context} to query the setting. + * @return The {@link ProxyInfo} which build from global http proxy settings. + */ + @Nullable + public static ProxyInfo getGlobalProxy(@NonNull Context context) { + final String host = Settings.Global.getString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST); + final int port = Settings.Global.getInt( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* def */); + final String exclusionList = Settings.Global.getString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST); + final String pacFileUrl = Settings.Global.getString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC); + + if (TextUtils.isEmpty(host) && TextUtils.isEmpty(pacFileUrl)) { + return null; // No global proxy. + } + + if (TextUtils.isEmpty(pacFileUrl)) { + return ProxyInfo.buildDirectProxy( + host, port, ProxyUtils.exclusionStringAsList(exclusionList)); + } else { + return ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl)); + } + } + + /** + * Set global http proxy settings from given {@link ProxyInfo}. + * + * @param context The {@link Context} to set the setting. + * @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from + * {@link ProxyInfo#buildPacProxy(Uri)} or + * {@link ProxyInfo#buildDirectProxy(String, int, List)} + */ + public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) { + final String host = proxyInfo.getHost(); + final int port = proxyInfo.getPort(); + final String exclusionList = proxyInfo.getExclusionListAsString(); + final String pacFileUrl = proxyInfo.getPacFileUrl().toString(); + + if (TextUtils.isEmpty(pacFileUrl)) { + Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host); + Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclusionList); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */); + } else { + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, pacFileUrl); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */); + Settings.Global.putInt( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */); + } + } + + /** + * Clear all global http proxy settings. + * + * @param context The {@link Context} to set the setting. + */ + public static void clearGlobalProxy(@NonNull Context context) { + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */); + Settings.Global.putInt( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */); + Settings.Global.putString( + context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */); + } + + /** + * Get specific private dns provider name from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The specific private dns provider name, or null if no setting value. + */ + @Nullable + public static String getPrivateDnsHostname(@NonNull Context context) { + return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER); + } + + /** + * Set specific private dns provider name to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param specifier The specific private dns provider name. + */ + public static void setPrivateDnsHostname(@NonNull Context context, + @Nullable String specifier) { + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier); + } + + /** + * Get default private dns mode from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The default private dns mode. + */ + @PrivateDnsMode + @NonNull + public static String getPrivateDnsDefaultMode(@NonNull Context context) { + return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE); + } + + /** + * Set default private dns mode to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param mode The default private dns mode. This should be one of the PRIVATE_DNS_MODE_* + * constants. + */ + public static void setPrivateDnsDefaultMode(@NonNull Context context, + @NonNull @PrivateDnsMode String mode) { + if (!(mode == PRIVATE_DNS_MODE_OFF + || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC + || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { + throw new IllegalArgumentException("Invalid private dns mode"); + } + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE, mode); + } + + /** + * Get duration (from {@link Settings}) to keep a PendingIntent-based request. + * + * @param context The {@link Context} to query the setting. + * @param def The default duration if no setting value. + * @return The duration to keep a PendingIntent-based request. + */ + @NonNull + public static Duration getConnectivityKeepPendingIntentDuration(@NonNull Context context, + @NonNull Duration def) { + final int duration = Settings.Secure.getInt(context.getContentResolver(), + CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, (int) def.toMillis()); + return Duration.ofMillis(duration); + } + + /** + * Set duration (to {@link Settings}) to keep a PendingIntent-based request. + * + * @param context The {@link Context} to set the setting. + * @param duration The duration to keep a PendingIntent-based request. + */ + public static void setConnectivityKeepPendingIntentDuration(@NonNull Context context, + @NonNull Duration duration) { + final int time = (int) duration.toMillis(); + if (time < 0) { + throw new IllegalArgumentException("Invalid duration."); + } + Settings.Secure.putInt( + context.getContentResolver(), CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, time); + } + + /** + * Read from {@link Settings} whether the mobile data connection should remain active + * even when higher priority networks are active. + * + * @param context The {@link Context} to query the setting. + * @param def The default value if no setting value. + * @return Whether the mobile data connection should remain active even when higher + * priority networks are active. + */ + public static boolean getMobileDataAlwaysOn(@NonNull Context context, boolean def) { + final int enable = Settings.Global.getInt( + context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (def ? 1 : 0)); + return (enable != 0) ? true : false; + } + + /** + * Write into {@link Settings} whether the mobile data connection should remain active + * even when higher priority networks are active. + * + * @param context The {@link Context} to set the setting. + * @param enable Whether the mobile data connection should remain active even when higher + * priority networks are active. + */ + public static void setMobileDataAlwaysOn(@NonNull Context context, boolean enable) { + Settings.Global.putInt( + context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (enable ? 1 : 0)); + } + + /** + * Read from {@link Settings} whether the wifi data connection should remain active + * even when higher priority networks are active. + * + * @param context The {@link Context} to query the setting. + * @param def The default value if no setting value. + * @return Whether the wifi data connection should remain active even when higher + * priority networks are active. + */ + public static boolean getWifiAlwaysRequested(@NonNull Context context, boolean def) { + final int enable = Settings.Global.getInt( + context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (def ? 1 : 0)); + return (enable != 0) ? true : false; + } + + /** + * Write into {@link Settings} whether the wifi data connection should remain active + * even when higher priority networks are active. + * + * @param context The {@link Context} to set the setting. + * @param enable Whether the wifi data connection should remain active even when higher + * priority networks are active + */ + public static void setWifiAlwaysRequested(@NonNull Context context, boolean enable) { + Settings.Global.putInt( + context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (enable ? 1 : 0)); + } + + /** + * Get avoid bad wifi setting from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The setting whether to automatically switch away from wifi networks that lose + * internet access. + */ + @NetworkAvoidBadWifi + public static int getNetworkAvoidBadWifi(@NonNull Context context) { + final String setting = + Settings.Global.getString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI); + if ("0".equals(setting)) { + return NETWORK_AVOID_BAD_WIFI_IGNORE; + } else if ("1".equals(setting)) { + return NETWORK_AVOID_BAD_WIFI_AVOID; + } else { + return NETWORK_AVOID_BAD_WIFI_PROMPT; + } + } + + /** + * Set avoid bad wifi setting to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param value Whether to automatically switch away from wifi networks that lose internet + * access. + */ + public static void setNetworkAvoidBadWifi(@NonNull Context context, + @NetworkAvoidBadWifi int value) { + final String setting; + if (value == NETWORK_AVOID_BAD_WIFI_IGNORE) { + setting = "0"; + } else if (value == NETWORK_AVOID_BAD_WIFI_AVOID) { + setting = "1"; + } else if (value == NETWORK_AVOID_BAD_WIFI_PROMPT) { + setting = null; + } else { + throw new IllegalArgumentException("Invalid avoid bad wifi setting"); + } + Settings.Global.putString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI, setting); + } + + /** + * Get network metered multipath preference from {@link Settings}. + * + * @param context The {@link Context} to query the setting. + * @return The network metered multipath preference which should be one of + * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value specified + * by config_networkMeteredMultipathPreference is used. + */ + @Nullable + public static String getNetworkMeteredMultipathPreference(@NonNull Context context) { + return Settings.Global.getString( + context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE); + } + + /** + * Set network metered multipath preference to {@link Settings}. + * + * @param context The {@link Context} to set the setting. + * @param preference The network metered multipath preference which should be one of + * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value + * specified by config_networkMeteredMultipathPreference is used. + */ + public static void setNetworkMeteredMultipathPreference(@NonNull Context context, + @NonNull @MultipathPreference String preference) { + if (!(Integer.valueOf(preference) == MULTIPATH_PREFERENCE_HANDOVER + || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_RELIABILITY + || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_PERFORMANCE)) { + throw new IllegalArgumentException("Invalid private dns mode"); + } + Settings.Global.putString( + context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference); + } } From 6865eb6ddaa4d96f99c0ab9509cc94b36c624abb Mon Sep 17 00:00:00 2001 From: paulhu Date: Thu, 25 Mar 2021 12:36:56 +0800 Subject: [PATCH 153/232] Add PRIVATE_DNS_MODE setting setter Bug: 182538166 Test: atest FrameworksNetTests Merged-In: If234426f041606c4881de1eca31b6f5bdb3c6bfe Change-Id: Iaa9e6cc92f1805ab341c308fc60e947ec4c674f0 --- framework/api/module-lib-current.txt | 1 + .../src/android/net/ConnectivityManager.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 3a4cebc93a..7fe2f18e39 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -20,6 +20,7 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean); + method public static void setPrivateDnsMode(@NonNull android.content.Context, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection>); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index bc668f3f0a..effdf5b811 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5371,4 +5371,23 @@ public class ConnectivityManager { if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC; return mode; } + + /** + * Set private DNS mode to settings. + * + * @param context The {@link Context} to set the private DNS mode. + * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public static void setPrivateDnsMode(@NonNull Context context, + @NonNull @PrivateDnsMode String mode) { + if (!(mode == PRIVATE_DNS_MODE_OFF + || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC + || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { + throw new IllegalArgumentException("Invalid private dns mode"); + } + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, mode); + } } From 7a4eeed62f0c05ceae24182df624fd92d2c9b547 Mon Sep 17 00:00:00 2001 From: paulhu Date: Thu, 25 Mar 2021 13:17:58 +0800 Subject: [PATCH 154/232] Add MOBILE_DATA_PREFERRED_APPS setting This setting is OEM upstream requirement for mobile data preferred apps feature. Bug: 171872461 Test: atest FrameworksNetTests Merged-In: Ic5e0515b2b948de3d333c8d8e073d0b15514562a Change-Id: Iba17bf68cffbe39d1c08ad94364b41bbf851bf57 --- framework/api/module-lib-current.txt | 2 ++ .../net/ConnectivitySettingsManager.java | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 7fe2f18e39..0266ea9ac1 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -52,6 +52,7 @@ package android.net { method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context); method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean); + method @Nullable public static String getMobileDataPreferredApps(@NonNull android.content.Context); method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context); method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context); method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int); @@ -69,6 +70,7 @@ package android.net { method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo); method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean); + method public static void setMobileDataPreferredApps(@NonNull android.content.Context, @Nullable String); method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int); method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String); method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int); diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index e133d5e53f..9a00055e00 100644 --- a/framework/src/android/net/ConnectivitySettingsManager.java +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -332,6 +332,14 @@ public class ConnectivitySettingsManager { public static final String NETWORK_METERED_MULTIPATH_PREFERENCE = "network_metered_multipath_preference"; + /** + * A list of apps that should go on cellular networks in preference even when higher-priority + * networks are connected. + * + * @hide + */ + public static final String MOBILE_DATA_PREFERRED_APPS = "mobile_data_preferred_apps"; + /** * Get mobile data activity timeout from {@link Settings}. * @@ -893,4 +901,29 @@ public class ConnectivitySettingsManager { Settings.Global.putString( context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference); } + + /** + * Get the list of apps(from {@link Settings}) that should go on cellular networks in preference + * even when higher-priority networks are connected. + * + * @param context The {@link Context} to query the setting. + * @return A list of apps that should go on cellular networks in preference even when + * higher-priority networks are connected or null if no setting value. + */ + @Nullable + public static String getMobileDataPreferredApps(@NonNull Context context) { + return Settings.Secure.getString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS); + } + + /** + * Set the list of apps(to {@link Settings}) that should go on cellular networks in preference + * even when higher-priority networks are connected. + * + * @param context The {@link Context} to set the setting. + * @param list A list of apps that should go on cellular networks in preference even when + * higher-priority networks are connected. + */ + public static void setMobileDataPreferredApps(@NonNull Context context, @Nullable String list) { + Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS, list); + } } From 8ad5812ebc17dacd6000b2671158b259cb7da19e Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 18 Mar 2021 00:54:57 +0900 Subject: [PATCH 155/232] Add onBlockedStatusChanged(Network, int) to NetworkCallback. This is similar to onBlockedStatusChanged(Network, boolean) but it allows the callback holder to know the exact reason why networking was blocked. It is useful to privileged system components such as JobScheduler that are able to ignore some blocked reasons but not others. Also add a new BLOCKED_REASON_LOCKDOWN_VPN that is used when networking is blocked because an always-on VPN is in lockdown mode. Also move BLOCKED_METERED_REASON_MASK to ConnectivityManager. This is necessary because ConnectivityService must ensure that the blocked status callbacks are correctly sent when meteredness changes (e.g., a UID that is blocked on metered networks will become unblocked on a network that becomes unmetered). In order to do this it needs to know which reasons apply only on metered networks. Bug: 165835257 Test: unit tests in subsequent CLs in the stack Change-Id: I647db4f5a01280be220288e73ffa85c15bec9370 --- framework/api/module-lib-current.txt | 6 ++ .../src/android/net/ConnectivityManager.java | 70 +++++++++++++++++-- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 0266ea9ac1..35e45ecb18 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -28,10 +28,12 @@ package android.net { method public void systemReady(); field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000 field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000 + field public static final int BLOCKED_METERED_REASON_MASK = -65536; // 0xffff0000 field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000 field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4 field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1 field public static final int BLOCKED_REASON_DOZE = 2; // 0x2 + field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10 field public static final int BLOCKED_REASON_NONE = 0; // 0x0 field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8 field public static final String PRIVATE_DNS_MODE_OFF = "off"; @@ -41,6 +43,10 @@ package android.net { field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 } + public static class ConnectivityManager.NetworkCallback { + method public void onBlockedStatusChanged(@NonNull android.net.Network, int); + } + public class ConnectivitySettingsManager { method public static void clearGlobalProxy(@NonNull android.content.Context); method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index effdf5b811..d196c1a2d1 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -38,7 +38,9 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -871,6 +873,17 @@ public class ConnectivityManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3; + /** + * Flag to indicate that an app is blocked because it is subject to an always-on VPN but the VPN + * is not currently connected. + * + * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean) + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_LOCKDOWN_VPN = 1 << 4; + /** * Flag to indicate that an app is subject to Data saver restrictions that would * result in its metered network access being blocked. @@ -914,6 +927,14 @@ public class ConnectivityManager { }) public @interface BlockedReason {} + /** + * Set of blocked reasons that are only applicable on metered networks. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_METERED_REASON_MASK = 0xffff0000; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; @@ -3442,12 +3463,30 @@ public class ConnectivityManager { * @param blocked Whether access to the {@link Network} is blocked due to system policy. * @hide */ - public void onAvailable(@NonNull Network network, + public final void onAvailable(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities, - @NonNull LinkProperties linkProperties, boolean blocked) { + @NonNull LinkProperties linkProperties, @BlockedReason int blocked) { // Internally only this method is called when a new network is available, and // it calls the callback in the same way and order that older versions used // to call so as not to change the behavior. + onAvailable(network, networkCapabilities, linkProperties, blocked != 0); + onBlockedStatusChanged(network, blocked); + } + + /** + * Legacy variant of onAvailable that takes a boolean blocked reason. + * + * This method has never been public API, but it's not final, so there may be apps that + * implemented it and rely on it being called. Do our best not to break them. + * Note: such apps will also get a second call to onBlockedStatusChanged immediately after + * this method is called. There does not seem to be a way to avoid this. + * TODO: add a compat check to move apps off this method, and eventually stop calling it. + * + * @hide + */ + public void onAvailable(@NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, boolean blocked) { onAvailable(network); if (!networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)) { @@ -3455,7 +3494,7 @@ public class ConnectivityManager { } onCapabilitiesChanged(network, networkCapabilities); onLinkPropertiesChanged(network, linkProperties); - onBlockedStatusChanged(network, blocked); + // No call to onBlockedStatusChanged here. That is done by the caller. } /** @@ -3619,6 +3658,26 @@ public class ConnectivityManager { */ public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {} + /** + * Called when access to the specified network is blocked or unblocked. + * + * If a NetworkCallback object implements this method, + * {@link #onBlockedStatusChanged(Network, boolean)} will not be called. + * + *

    Do NOT call {@link #getNetworkCapabilities(Network)} or + * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in + * this callback as this is prone to race conditions : calling these methods while in a + * callback may return an outdated or even a null object. + * + * @param network The {@link Network} whose blocked status has changed. + * @param blocked The blocked status of this {@link Network}. + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + public void onBlockedStatusChanged(@NonNull Network network, @BlockedReason int blocked) { + onBlockedStatusChanged(network, blocked != 0); + } + private NetworkRequest networkRequest; private final int mFlags; } @@ -3733,7 +3792,7 @@ public class ConnectivityManager { case CALLBACK_AVAILABLE: { NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); LinkProperties lp = getObject(message, LinkProperties.class); - callback.onAvailable(network, cap, lp, message.arg1 != 0); + callback.onAvailable(network, cap, lp, message.arg1); break; } case CALLBACK_LOSING: { @@ -3767,8 +3826,7 @@ public class ConnectivityManager { break; } case CALLBACK_BLK_CHANGED: { - boolean blocked = message.arg1 != 0; - callback.onBlockedStatusChanged(network, blocked); + callback.onBlockedStatusChanged(network, message.arg1); } } } From 8fc279e0cc1437cdbfd366fddb19e3ed902f22c7 Mon Sep 17 00:00:00 2001 From: Sarah Chin Date: Mon, 22 Mar 2021 13:52:46 -0700 Subject: [PATCH 156/232] API to get network visible network capability name Test: atest DataConnectionTest, DcTrackerTest Bug: 181916712 CTS-Coverage-Bug: 183553812 Change-Id: Iae63ac4d62641cee2bd0f0c5f50dd729750d514c Merged-In: Iae63ac4d62641cee2bd0f0c5f50dd729750d514c --- framework/api/system-current.txt | 1 + .../src/android/net/NetworkCapabilities.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 8e6e846240..997bf6a1e6 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -264,6 +264,7 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { method @NonNull public int[] getAdministratorUids(); + method @Nullable public static String getCapabilityCarrierName(int); method @Nullable public String getSsid(); method @NonNull public int[] getTransportTypes(); method public boolean isPrivateDnsBroken(); diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 881fa8c270..76208648aa 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -749,6 +749,23 @@ public final class NetworkCapabilities implements Parcelable { return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0); } + /** + * Get the name of the given capability that carriers use. + * If the capability does not have a carrier-name, returns null. + * + * @param capability The capability to get the carrier-name of. + * @return The carrier-name of the capability, or null if it doesn't exist. + * @hide + */ + @SystemApi + public static @Nullable String getCapabilityCarrierName(@NetCapability int capability) { + if (capability == NET_CAPABILITY_ENTERPRISE) { + return capabilityNameOf(capability); + } else { + return null; + } + } + private void combineNetCapabilities(@NonNull NetworkCapabilities nc) { final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities; final long unwantedCaps = From 1c5f57f921042f649fe388869fc3ba7a44fb1d25 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Thu, 25 Mar 2021 17:51:00 +0800 Subject: [PATCH 157/232] Add network disconnected callback Create a network callback to notify network agent after the native network being destroyed by netd which means the network is fully disconnected. The NetworkAgent may handle this event after sending disconnect state to ConnectivityService to proceed its pending works that have to be done after it. Bug: 178725261 Test: make update-api Change-Id: I602ff2c688909473b03b72c9407d4286608cff4c Merged-In: I602ff2c688909473b03b72c9407d4286608cff4c --- framework/api/system-current.txt | 1 + framework/src/android/net/INetworkAgent.aidl | 1 + framework/src/android/net/NetworkAgent.java | 23 ++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 8e6e846240..6f832c6d3d 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -218,6 +218,7 @@ package android.net { method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); method public void onAutomaticReconnectDisabled(); method public void onNetworkCreated(); + method public void onNetworkDisconnected(); method public void onNetworkUnwanted(); method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); method public void onQosCallbackUnregistered(int); diff --git a/framework/src/android/net/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl index 078acbd8fe..f9d399459e 100644 --- a/framework/src/android/net/INetworkAgent.aidl +++ b/framework/src/android/net/INetworkAgent.aidl @@ -47,4 +47,5 @@ oneway interface INetworkAgent { void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel); void onQosCallbackUnregistered(int qosCallbackId); void onNetworkCreated(); + void onNetworkDisconnected(); } diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index aef1a31b72..01b88aa032 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -370,6 +370,14 @@ public abstract class NetworkAgent { */ public static final int CMD_NETWORK_CREATED = BASE + 22; + /** + * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native + * network was destroyed. + * + * @hide + */ + public static final int CMD_NETWORK_DISCONNECTED = BASE + 23; + private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { // The subtype can be changed with (TODO) setLegacySubtype, but it starts // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description. @@ -574,6 +582,10 @@ public abstract class NetworkAgent { onNetworkCreated(); break; } + case CMD_NETWORK_DISCONNECTED: { + onNetworkDisconnected(); + break; + } } } } @@ -719,6 +731,11 @@ public abstract class NetworkAgent { public void onNetworkCreated() { mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_CREATED)); } + + @Override + public void onNetworkDisconnected() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DISCONNECTED)); + } } /** @@ -1031,6 +1048,12 @@ public abstract class NetworkAgent { */ public void onNetworkCreated() {} + + /** + * Called when ConnectivityService has successfully destroy this NetworkAgent's native network. + */ + public void onNetworkDisconnected() {} + /** * Requests that the network hardware send the specified packet at the specified interval. * From 7f7c26b429590b46cf5454c371aa5d81109cc5b2 Mon Sep 17 00:00:00 2001 From: Aaron Huang Date: Fri, 19 Mar 2021 22:56:26 +0800 Subject: [PATCH 158/232] Move deduceRestrictedCapability to libs/net and rename it NetworkCapabilities is included in framework-connectivity, so external module cannot have dependencies on its hidden API. Move the method to libs/net so that external modules can use it by including the library. Bug: 178777253 Test: FrameworksNetTests (cherry-picked from ag/13921626) Merged-In: I77970b3a5e5e0e9d263639694b1f06519169bf64 Change-Id: I77970b3a5e5e0e9d263639694b1f06519169bf64 --- .../src/android/net/NetworkCapabilities.java | 66 +------------------ 1 file changed, 2 insertions(+), 64 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 881fa8c270..49248518d2 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -537,43 +537,6 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_TRUSTED) | (1 << NET_CAPABILITY_NOT_VPN); - /** - * Capabilities that suggest that a network is restricted. - * {@see #maybeMarkCapabilitiesRestricted}, {@see #FORCE_RESTRICTED_CAPABILITIES} - */ - @VisibleForTesting - /* package */ static final long RESTRICTED_CAPABILITIES = - (1 << NET_CAPABILITY_CBS) - | (1 << NET_CAPABILITY_DUN) - | (1 << NET_CAPABILITY_EIMS) - | (1 << NET_CAPABILITY_FOTA) - | (1 << NET_CAPABILITY_IA) - | (1 << NET_CAPABILITY_IMS) - | (1 << NET_CAPABILITY_MCX) - | (1 << NET_CAPABILITY_RCS) - | (1 << NET_CAPABILITY_VEHICLE_INTERNAL) - | (1 << NET_CAPABILITY_XCAP) - | (1 << NET_CAPABILITY_ENTERPRISE); - - /** - * Capabilities that force network to be restricted. - * {@see #maybeMarkCapabilitiesRestricted}. - */ - private static final long FORCE_RESTRICTED_CAPABILITIES = - (1 << NET_CAPABILITY_OEM_PAID) - | (1 << NET_CAPABILITY_OEM_PRIVATE); - - /** - * Capabilities that suggest that a network is unrestricted. - * {@see #maybeMarkCapabilitiesRestricted}. - */ - @VisibleForTesting - /* package */ static final long UNRESTRICTED_CAPABILITIES = - (1 << NET_CAPABILITY_INTERNET) - | (1 << NET_CAPABILITY_MMS) - | (1 << NET_CAPABILITY_SUPL) - | (1 << NET_CAPABILITY_WIFI_P2P); - /** * Capabilities that are managed by ConnectivityService. */ @@ -811,37 +774,12 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Deduces that all the capabilities it provides are typically provided by restricted networks - * or not. - * - * @return {@code true} if the network should be restricted. - * @hide - */ - public boolean deduceRestrictedCapability() { - // Check if we have any capability that forces the network to be restricted. - final boolean forceRestrictedCapability = - (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0; - - // Verify there aren't any unrestricted capabilities. If there are we say - // the whole thing is unrestricted unless it is forced to be restricted. - final boolean hasUnrestrictedCapabilities = - (mNetworkCapabilities & UNRESTRICTED_CAPABILITIES) != 0; - - // Must have at least some restricted capabilities. - final boolean hasRestrictedCapabilities = - (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0; - - return forceRestrictedCapability - || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities); - } - - /** - * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted. + * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if inferring the network is restricted. * * @hide */ public void maybeMarkCapabilitiesRestricted() { - if (deduceRestrictedCapability()) { + if (NetworkCapabilitiesUtils.inferRestrictedCapability(this)) { removeCapability(NET_CAPABILITY_NOT_RESTRICTED); } } From eab4a9c9842c454f3353281cf4a2751f52e544ed Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Thu, 25 Mar 2021 14:28:15 +0800 Subject: [PATCH 159/232] Expose BIP and VSIM relavent definitions In order to support special APNs below, OEM may need extra NetworkCapabilities and apn type definition to support the carriers request. Add corresponding definition into API surface. VSIM: for Virtual SIM service BIP: for Bearer Independent Protocol Bug: 130869457 Test: make update-api Change-Id: I41e881c6fe39e92d5cdac2d0a02fa8a8e814c9c5 Merged-In: I41e881c6fe39e92d5cdac2d0a02fa8a8e814c9c5 --- framework/api/system-current.txt | 2 ++ .../src/android/net/NetworkCapabilities.java | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 8e6e846240..b64657e55e 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -268,11 +268,13 @@ package android.net { method @NonNull public int[] getTransportTypes(); method public boolean isPrivateDnsBroken(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); + field public static final int NET_CAPABILITY_BIP = 31; // 0x1f field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b + field public static final int NET_CAPABILITY_VSIM = 30; // 0x1e } public static final class NetworkCapabilities.Builder { diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 49248518d2..7e7089176e 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -274,6 +274,8 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_VEHICLE_INTERNAL, NET_CAPABILITY_NOT_VCN_MANAGED, NET_CAPABILITY_ENTERPRISE, + NET_CAPABILITY_VSIM, + NET_CAPABILITY_BIP, }) public @interface NetCapability { } @@ -493,8 +495,22 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int NET_CAPABILITY_ENTERPRISE = 29; + /** + * Indicates that this network has ability to access the carrier's Virtual Sim service. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_VSIM = 30; + + /** + * Indicates that this network has ability to support Bearer Independent Protol. + * @hide + */ + @SystemApi + public static final int NET_CAPABILITY_BIP = 31; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_ENTERPRISE; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_BIP; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -2101,6 +2117,8 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_VEHICLE_INTERNAL: return "VEHICLE_INTERNAL"; case NET_CAPABILITY_NOT_VCN_MANAGED: return "NOT_VCN_MANAGED"; case NET_CAPABILITY_ENTERPRISE: return "ENTERPRISE"; + case NET_CAPABILITY_VSIM: return "VSIM"; + case NET_CAPABILITY_BIP: return "BIP"; default: return Integer.toString(capability); } } From 3f1a21ee00bdc84de3bf65e49438446a515bc86f Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Tue, 23 Mar 2021 21:01:07 +0900 Subject: [PATCH 160/232] Re-implement NetworkUtils#queryUserAccess. Currently, queryUserAccess talks to netd via FwmarkServer. Doing this from the module would require exposing queryUserAccess as an NDK API or reimplementing FwmarkClient. Because queryUserAccess really only uses information that comes from ConnectivityService/PermissionMonitor anyway, just use that information without calling to net. Test: atest HostsideVpnTests Bug: 171540887 Merged-In: If855de1ea3e1fd2ed30f2795d9b4acfcf969a2dc Change-Id: If855de1ea3e1fd2ed30f2795d9b4acfcf969a2dc --- framework/src/android/net/NetworkUtils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java index c0f262815b..da3350ef62 100644 --- a/framework/src/android/net/NetworkUtils.java +++ b/framework/src/android/net/NetworkUtils.java @@ -92,7 +92,10 @@ public class NetworkUtils { * Determine if {@code uid} can access network designated by {@code netId}. * @return {@code true} if {@code uid} can access network, {@code false} otherwise. */ - public native static boolean queryUserAccess(int uid, int netId); + public static boolean queryUserAccess(int uid, int netId) { + // TODO (b/183485986): remove this method + return false; + } /** * DNS resolver series jni method. From d90927f2183c4641923b9f2d91d5eae4b20c91ce Mon Sep 17 00:00:00 2001 From: lifr Date: Thu, 18 Mar 2021 01:11:30 +0800 Subject: [PATCH 161/232] [TL02]Remove hidden API usage of NetworkAgent The connection service will become the mainline module. Remove the hidden API usage of NetworkAgent. Bug: 170598012 CTS-Coverage-Bug: 170598012 Test: atest FrameworksNetTests FrameworksTelephonyTests atest FrameworksWifiTests Change-Id: I4e4040ae7f94bdf479c7df9ec2ffabafbe06331c Merged-In: I4e4040ae7f94bdf479c7df9ec2ffabafbe06331c --- framework/api/system-current.txt | 8 ++ framework/src/android/net/NetworkAgent.java | 7 +- .../src/android/net/NetworkAgentConfig.java | 45 +++++++++- .../src/android/net/SocketKeepalive.java | 84 ++++++++++++++----- 4 files changed, 119 insertions(+), 25 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 6f832c6d3d..1ee79a425e 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -217,6 +217,7 @@ package android.net { method public void markConnected(); method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); method public void onAutomaticReconnectDisabled(); + method public void onBandwidthUpdateRequested(); method public void onNetworkCreated(); method public void onNetworkDisconnected(); method public void onNetworkUnwanted(); @@ -236,6 +237,7 @@ package android.net { method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes); method public final void sendQosSessionLost(int, int, int); method public final void sendSocketKeepaliveEvent(int, int); + method @Deprecated public void setLegacySubtype(int, @NonNull String); method public final void setUnderlyingNetworks(@Nullable java.util.List); method public void unregister(); field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 @@ -256,7 +258,12 @@ package android.net { public static final class NetworkAgentConfig.Builder { ctor public NetworkAgentConfig.Builder(); method @NonNull public android.net.NetworkAgentConfig build(); + method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection(); + method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification(); method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); @@ -405,6 +412,7 @@ package android.net { } public abstract class SocketKeepalive implements java.lang.AutoCloseable { + field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf field public static final int SUCCESS = 0; // 0x0 } diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index 01b88aa032..6b55bb771c 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -379,9 +379,8 @@ public abstract class NetworkAgent { public static final int CMD_NETWORK_DISCONNECTED = BASE + 23; private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { - // The subtype can be changed with (TODO) setLegacySubtype, but it starts - // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description. - final NetworkInfo ni = new NetworkInfo(config.legacyType, 0, config.legacyTypeName, ""); + final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType, + config.legacyTypeName, config.legacySubTypeName); ni.setIsAvailable(true); ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */, config.getLegacyExtraInfo()); @@ -863,6 +862,7 @@ public abstract class NetworkAgent { * @hide */ @Deprecated + @SystemApi public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) { mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName); queueOrSendNetworkInfo(mNetworkInfo); @@ -996,6 +996,7 @@ public abstract class NetworkAgent { * shall try to overwrite this method and produce a bandwidth update if capable. * @hide */ + @SystemApi public void onBandwidthUpdateRequested() { pollLceData(); } diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java index 0bd2371bfc..3f058d8cbf 100644 --- a/framework/src/android/net/NetworkAgentConfig.java +++ b/framework/src/android/net/NetworkAgentConfig.java @@ -174,6 +174,12 @@ public final class NetworkAgentConfig implements Parcelable { return legacyType; } + /** + * The legacy Sub type of this network agent, or TYPE_NONE if unset. + * @hide + */ + public int legacySubType = ConnectivityManager.TYPE_NONE; + /** * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network. * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode. @@ -199,6 +205,13 @@ public final class NetworkAgentConfig implements Parcelable { return legacyTypeName; } + /** + * The name of the legacy Sub network type. It's a free-form string. + * @hide + */ + @NonNull + public String legacySubTypeName = ""; + /** * The legacy extra info of the agent. The extra info should only be : *

      @@ -235,6 +248,8 @@ public final class NetworkAgentConfig implements Parcelable { skip464xlat = nac.skip464xlat; legacyType = nac.legacyType; legacyTypeName = nac.legacyTypeName; + legacySubType = nac.legacySubType; + legacySubTypeName = nac.legacySubTypeName; mLegacyExtraInfo = nac.mLegacyExtraInfo; } } @@ -300,7 +315,6 @@ public final class NetworkAgentConfig implements Parcelable { * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64. * * @return this builder, to facilitate chaining. - * @hide */ @NonNull public Builder disableNat64Detection() { @@ -313,7 +327,6 @@ public final class NetworkAgentConfig implements Parcelable { * perform its own carrier-specific provisioning procedure. * * @return this builder, to facilitate chaining. - * @hide */ @NonNull public Builder disableProvisioningNotification() { @@ -333,6 +346,18 @@ public final class NetworkAgentConfig implements Parcelable { return this; } + /** + * Sets the legacy sub-type for this network. + * + * @param legacySubType the type + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setLegacySubType(final int legacySubType) { + mConfig.legacySubType = legacySubType; + return this; + } + /** * Sets the name of the legacy type of the agent. It's a free-form string used in logging. * @param legacyTypeName the name @@ -344,11 +369,21 @@ public final class NetworkAgentConfig implements Parcelable { return this; } + /** + * Sets the name of the legacy Sub-type of the agent. It's a free-form string. + * @param legacySubTypeName the name + * @return this builder, to facilitate chaining. + */ + @NonNull + public Builder setLegacySubTypeName(@NonNull String legacySubTypeName) { + mConfig.legacySubTypeName = legacySubTypeName; + return this; + } + /** * Sets the legacy extra info of the agent. * @param legacyExtraInfo the legacy extra info. * @return this builder, to facilitate chaining. - * @hide */ @NonNull public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) { @@ -435,6 +470,8 @@ public final class NetworkAgentConfig implements Parcelable { out.writeInt(skip464xlat ? 1 : 0); out.writeInt(legacyType); out.writeString(legacyTypeName); + out.writeInt(legacySubType); + out.writeString(legacySubTypeName); out.writeString(mLegacyExtraInfo); } @@ -452,6 +489,8 @@ public final class NetworkAgentConfig implements Parcelable { networkAgentConfig.skip464xlat = in.readInt() != 0; networkAgentConfig.legacyType = in.readInt(); networkAgentConfig.legacyTypeName = in.readString(); + networkAgentConfig.legacySubType = in.readInt(); + networkAgentConfig.legacySubTypeName = in.readString(); networkAgentConfig.mLegacyExtraInfo = in.readString(); return networkAgentConfig; } diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java index d007a9520c..f6cae72516 100644 --- a/framework/src/android/net/SocketKeepalive.java +++ b/framework/src/android/net/SocketKeepalive.java @@ -55,36 +55,68 @@ public abstract class SocketKeepalive implements AutoCloseable { static final String TAG = "SocketKeepalive"; /** - * No errors. + * Success. It indicates there is no error. * @hide */ @SystemApi public static final int SUCCESS = 0; - /** @hide */ + /** + * No keepalive. This should only be internally as it indicates There is no keepalive. + * It should not propagate to applications. + * @hide + */ public static final int NO_KEEPALIVE = -1; - /** @hide */ + /** + * Data received. + * @hide + */ public static final int DATA_RECEIVED = -2; - /** @hide */ + /** + * The binder died. + * @hide + */ public static final int BINDER_DIED = -10; - /** The specified {@code Network} is not connected. */ + /** + * The invalid network. It indicates the specified {@code Network} is not connected. + */ public static final int ERROR_INVALID_NETWORK = -20; - /** The specified IP addresses are invalid. For example, the specified source IP address is - * not configured on the specified {@code Network}. */ + + /** + * The invalid IP addresses. Indicates the specified IP addresses are invalid. + * For example, the specified source IP address is not configured on the + * specified {@code Network}. + */ public static final int ERROR_INVALID_IP_ADDRESS = -21; - /** The requested port is invalid. */ + + /** + * The port is invalid. + */ public static final int ERROR_INVALID_PORT = -22; - /** The packet length is invalid (e.g., too long). */ + + /** + * The length is invalid (e.g. too long). + */ public static final int ERROR_INVALID_LENGTH = -23; - /** The packet transmission interval is invalid (e.g., too short). */ + + /** + * The interval is invalid (e.g. too short). + */ public static final int ERROR_INVALID_INTERVAL = -24; - /** The target socket is invalid. */ + + /** + * The socket is invalid. + */ public static final int ERROR_INVALID_SOCKET = -25; - /** The target socket is not idle. */ + + /** + * The socket is not idle. + */ public static final int ERROR_SOCKET_NOT_IDLE = -26; + /** * The stop reason is uninitialized. This should only be internally used as initial state * of stop reason, instead of propagating to application. @@ -92,15 +124,29 @@ public abstract class SocketKeepalive implements AutoCloseable { */ public static final int ERROR_STOP_REASON_UNINITIALIZED = -27; - /** The device does not support this request. */ + /** + * The request is unsupported. + */ public static final int ERROR_UNSUPPORTED = -30; - /** @hide TODO: delete when telephony code has been updated. */ - public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED; - /** The hardware returned an error. */ + + /** + * There was a hardware error. + */ public static final int ERROR_HARDWARE_ERROR = -31; - /** The limitation of resource is reached. */ + + /** + * Resources are insufficient (e.g. all hardware slots are in use). + */ public static final int ERROR_INSUFFICIENT_RESOURCES = -32; + /** + * There was no such slot. This should only be internally as it indicates + * a programming error in the system server. It should not propagate to + * applications. + * @hide + */ + @SystemApi + public static final int ERROR_NO_SUCH_SLOT = -33; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -111,7 +157,8 @@ public abstract class SocketKeepalive implements AutoCloseable { ERROR_INVALID_LENGTH, ERROR_INVALID_INTERVAL, ERROR_INVALID_SOCKET, - ERROR_SOCKET_NOT_IDLE + ERROR_SOCKET_NOT_IDLE, + ERROR_NO_SUCH_SLOT }) public @interface ErrorCode {} @@ -122,7 +169,6 @@ public abstract class SocketKeepalive implements AutoCloseable { ERROR_INVALID_LENGTH, ERROR_UNSUPPORTED, ERROR_INSUFFICIENT_RESOURCES, - ERROR_HARDWARE_UNSUPPORTED }) public @interface KeepaliveEvent {} From a1bd6f6d512e5c3ee4c3e3f58b7b22918b455645 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 25 Mar 2021 23:17:36 +0900 Subject: [PATCH 162/232] Address comments on onBlockedStatusChanged(Network, int) CL. Test: m Bug: 165835257 Change-Id: I6d3007a1eac54ee6650b350aee56ed398a2c950d --- framework/src/android/net/ConnectivityManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index d196c1a2d1..cb3f41c66a 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -921,6 +921,7 @@ public class ConnectivityManager { BLOCKED_REASON_DOZE, BLOCKED_REASON_APP_STANDBY, BLOCKED_REASON_RESTRICTED_MODE, + BLOCKED_REASON_LOCKDOWN_VPN, BLOCKED_METERED_REASON_DATA_SAVER, BLOCKED_METERED_REASON_USER_RESTRICTED, BLOCKED_METERED_REASON_ADMIN_DISABLED, @@ -3659,7 +3660,8 @@ public class ConnectivityManager { public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {} /** - * Called when access to the specified network is blocked or unblocked. + * Called when access to the specified network is blocked or unblocked, or the reason for + * access being blocked changes. * * If a NetworkCallback object implements this method, * {@link #onBlockedStatusChanged(Network, boolean)} will not be called. From 9364b3a40637550c769913490c28b71a9dcf6952 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 11 Mar 2021 01:32:09 +0900 Subject: [PATCH 163/232] Add a setTeardownDelayMs API to NetworkAgent. This allows transports to request that when the network is disconnected, the system should delay destroying the native network until the specified time has passed after the network disconnected. Bug: 181941583 Test: next CL in the stack Change-Id: I9765f1c9d1e55c23c6d583d6709dbe06505975b1 --- framework/api/system-current.txt | 3 +- framework/src/android/net/INetworkAgent.aidl | 2 +- .../android/net/INetworkAgentRegistry.aidl | 1 + framework/src/android/net/NetworkAgent.java | 50 ++++++++++++++++--- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 1ee79a425e..95ad694293 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -219,7 +219,7 @@ package android.net { method public void onAutomaticReconnectDisabled(); method public void onBandwidthUpdateRequested(); method public void onNetworkCreated(); - method public void onNetworkDisconnected(); + method public void onNetworkDestroyed(); method public void onNetworkUnwanted(); method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); method public void onQosCallbackUnregistered(int); @@ -238,6 +238,7 @@ package android.net { method public final void sendQosSessionLost(int, int, int); method public final void sendSocketKeepaliveEvent(int, int); method @Deprecated public void setLegacySubtype(int, @NonNull String); + method public void setTeardownDelayMs(@IntRange(from=0, to=0x1388) int); method public final void setUnderlyingNetworks(@Nullable java.util.List); method public void unregister(); field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 diff --git a/framework/src/android/net/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl index f9d399459e..d941d4b95b 100644 --- a/framework/src/android/net/INetworkAgent.aidl +++ b/framework/src/android/net/INetworkAgent.aidl @@ -47,5 +47,5 @@ oneway interface INetworkAgent { void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel); void onQosCallbackUnregistered(int qosCallbackId); void onNetworkCreated(); - void onNetworkDisconnected(); + void onNetworkDestroyed(); } diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl index cbd6193744..26cb1ed6b4 100644 --- a/framework/src/android/net/INetworkAgentRegistry.aidl +++ b/framework/src/android/net/INetworkAgentRegistry.aidl @@ -41,4 +41,5 @@ oneway interface INetworkAgentRegistry { void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes); void sendQosSessionLost(int qosCallbackId, in QosSession session); void sendQosCallbackError(int qosCallbackId, int exceptionType); + void sendTeardownDelayMs(int teardownDelayMs); } diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index 6b55bb771c..c57da53f28 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -184,6 +184,20 @@ public abstract class NetworkAgent { */ public static final int EVENT_UNDERLYING_NETWORKS_CHANGED = BASE + 5; + /** + * Sent by the NetworkAgent to ConnectivityService to pass the current value of the teardown + * delay. + * arg1 = teardown delay in milliseconds + * @hide + */ + public static final int EVENT_TEARDOWN_DELAY_CHANGED = BASE + 6; + + /** + * The maximum value for the teardown delay, in milliseconds. + * @hide + */ + public static final int MAX_TEARDOWN_DELAY_MS = 5000; + /** * Sent by ConnectivityService to the NetworkAgent to inform the agent of the * networks status - whether we could use the network or could not, due to @@ -197,7 +211,6 @@ public abstract class NetworkAgent { */ public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7; - /** * Network validation suceeded. * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}. @@ -376,7 +389,7 @@ public abstract class NetworkAgent { * * @hide */ - public static final int CMD_NETWORK_DISCONNECTED = BASE + 23; + public static final int CMD_NETWORK_DESTROYED = BASE + 23; private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType, @@ -581,8 +594,8 @@ public abstract class NetworkAgent { onNetworkCreated(); break; } - case CMD_NETWORK_DISCONNECTED: { - onNetworkDisconnected(); + case CMD_NETWORK_DESTROYED: { + onNetworkDestroyed(); break; } } @@ -732,8 +745,8 @@ public abstract class NetworkAgent { } @Override - public void onNetworkDisconnected() { - mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DISCONNECTED)); + public void onNetworkDestroyed() { + mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DESTROYED)); } } @@ -850,6 +863,29 @@ public abstract class NetworkAgent { queueOrSendNetworkInfo(mNetworkInfo); } + /** + * Sets the value of the teardown delay. + * + * The teardown delay is the time between when the network disconnects and when the native + * network corresponding to this {@code NetworkAgent} is destroyed. By default, the native + * network is destroyed immediately. If {@code teardownDelayMs} is non-zero, then when this + * network disconnects, the system will instead immediately mark the network as restricted + * and unavailable to unprivileged apps, but will defer destroying the native network until the + * teardown delay timer expires. + * + * The interfaces in use by this network will remain in use until the native network is + * destroyed and cannot be reused until {@link #onNetworkDestroyed()} is called. + * + * This method may be called at any time while the network is connected. It has no effect if + * the network is already disconnected and the teardown delay timer is running. + * + * @param teardownDelayMs the teardown delay to set, or 0 to disable teardown delay. + */ + public void setTeardownDelayMs( + @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int teardownDelayMs) { + queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMs)); + } + /** * Change the legacy subtype of this network agent. * @@ -1053,7 +1089,7 @@ public abstract class NetworkAgent { /** * Called when ConnectivityService has successfully destroy this NetworkAgent's native network. */ - public void onNetworkDisconnected() {} + public void onNetworkDestroyed() {} /** * Requests that the network hardware send the specified packet at the specified interval. From b4dab37e28bf71a7f471848ce9abb69cffc0d6f6 Mon Sep 17 00:00:00 2001 From: lifr Date: Thu, 11 Mar 2021 20:11:09 +0800 Subject: [PATCH 164/232] [JS01]Remove hidden API usage of Connectivity Sevice The Connectivity service will become the mainline module. Therefore, remove the caller of using Connectivity's hidden API outside the module and expose the required connectivity API used in Jobscheduler. Bug: 183456204 CTS-Coverage-Bug: 170598012 Test: atest JobStoreTest Change-Id: Ie6bc81ff382fb242b98f35d28a96defc207c7987 Merged-In: Ie6bc81ff382fb242b98f35d28a96defc207c7987 (cherry-picked from ag/13946348) --- framework/api/current.txt | 3 ++ framework/api/module-lib-current.txt | 1 + .../src/android/net/ConnectivityManager.java | 3 +- framework/src/android/net/NetworkRequest.java | 39 +++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index ad44b27f6d..0a9560a5c5 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -68,6 +68,7 @@ package android.net { method public boolean bindProcessToNetwork(@Nullable android.net.Network); method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork(); + method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public android.net.Network getActiveNetworkForUid(int); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks(); @@ -387,7 +388,9 @@ package android.net { public class NetworkRequest implements android.os.Parcelable { method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities); method public int describeContents(); + method @NonNull public int[] getCapabilities(); method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); + method @NonNull public int[] getTransportTypes(); method public boolean hasCapability(int); method public boolean hasTransport(int); method public void writeToParcel(android.os.Parcel, int); diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 35e45ecb18..cd96a1b481 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -120,6 +120,7 @@ package android.net { } public class NetworkRequest implements android.os.Parcelable { + method @NonNull public int[] getUnwantedCapabilities(); method public boolean hasUnwantedCapability(int); } diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index d196c1a2d1..93a84646a7 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1189,8 +1189,7 @@ public class ConnectivityManager { * * @return a {@link Network} object for the current default network for the * given UID or {@code null} if no default network is currently active - * - * @hide + * TODO: b/183465229 Cleanup getActiveNetworkForUid once b/165835257 is fixed */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) @Nullable diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index bcbc04f72e..38691ef5cb 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -699,4 +699,43 @@ public class NetworkRequest implements Parcelable { public int hashCode() { return Objects.hash(requestId, legacyType, networkCapabilities, type); } + + /** + * Gets all the capabilities set on this {@code NetworkRequest} instance. + * + * @return an array of capability values for this instance. + */ + @NonNull + public @NetCapability int[] getCapabilities() { + // No need to make a defensive copy here as NC#getCapabilities() already returns + // a new array. + return networkCapabilities.getCapabilities(); + } + + /** + * Gets all the unwanted capabilities set on this {@code NetworkRequest} instance. + * + * @return an array of unwanted capability values for this instance. + * + * @hide + */ + @NonNull + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public @NetCapability int[] getUnwantedCapabilities() { + // No need to make a defensive copy here as NC#getUnwantedCapabilities() already returns + // a new array. + return networkCapabilities.getUnwantedCapabilities(); + } + + /** + * Gets all the transports set on this {@code NetworkRequest} instance. + * + * @return an array of transport type values for this instance. + */ + @NonNull + public @Transport int[] getTransportTypes() { + // No need to make a defensive copy here as NC#getTransportTypes() already returns + // a new array. + return networkCapabilities.getTransportTypes(); + } } From 65b9f961591beabbd86e713a2a547f0695e96122 Mon Sep 17 00:00:00 2001 From: lifr Date: Wed, 24 Mar 2021 18:38:37 +0800 Subject: [PATCH 165/232] Add session ID to VpnTransportInfo - This will be visible only to apps with the NETWORK_SETTINGS permissions (signature), and will be redacted for all other callers. - This string is expected to be the same as set by VpnService#setSession, and in general, VpnConfig.session. But it will be a general API that Vpn.java can call when setting the VpnTransportInfo. - This string cannot be updated once the VPN NetworkAgent is connected. Bug: 171872481 Test: atest ConnectivityServiceTest atest VpnTransportInfoTest atest android.net.cts.NetworkAgentTest Change-Id: I8d09e25b83f7ee8be21ec9c9bd3c72a251f1370d Merged-In: I8d09e25b83f7ee8be21ec9c9bd3c72a251f1370d (cherry-picked from ag/14011912) --- framework/api/module-lib-current.txt | 4 ++- .../src/android/net/VpnTransportInfo.java | 32 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index cd96a1b481..f2f5c1b7f7 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -176,10 +176,12 @@ package android.net { } public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { - ctor public VpnTransportInfo(int); + ctor public VpnTransportInfo(int, @Nullable String); method public int describeContents(); + method @NonNull public android.net.VpnTransportInfo makeCopy(long); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field @Nullable public final String sessionId; field public final int type; } diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java index cd8f4c06de..ba83a44d0d 100644 --- a/framework/src/android/net/VpnTransportInfo.java +++ b/framework/src/android/net/VpnTransportInfo.java @@ -17,11 +17,14 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import java.util.Objects; @@ -38,8 +41,26 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { /** Type of this VPN. */ public final int type; - public VpnTransportInfo(int type) { + @Nullable + public final String sessionId; + + @Override + public long getApplicableRedactions() { + return REDACT_FOR_NETWORK_SETTINGS; + } + + /** + * Create a copy of a {@link VpnTransportInfo} with the sessionId redacted if necessary. + */ + @NonNull + public VpnTransportInfo makeCopy(long redactions) { + return new VpnTransportInfo(type, + ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : sessionId); + } + + public VpnTransportInfo(int type, @Nullable String sessionId) { this.type = type; + this.sessionId = sessionId; } @Override @@ -47,17 +68,17 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { if (!(o instanceof VpnTransportInfo)) return false; VpnTransportInfo that = (VpnTransportInfo) o; - return this.type == that.type; + return (this.type == that.type) && TextUtils.equals(this.sessionId, that.sessionId); } @Override public int hashCode() { - return Objects.hash(type); + return Objects.hash(type, sessionId); } @Override public String toString() { - return String.format("VpnTransportInfo{type=%d}", type); + return String.format("VpnTransportInfo{type=%d, sessionId=%s}", type, sessionId); } @Override @@ -68,12 +89,13 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(type); + dest.writeString(sessionId); } public static final @NonNull Creator CREATOR = new Creator() { public VpnTransportInfo createFromParcel(Parcel in) { - return new VpnTransportInfo(in.readInt()); + return new VpnTransportInfo(in.readInt(), in.readString()); } public VpnTransportInfo[] newArray(int size) { return new VpnTransportInfo[size]; From a732a85f7e9d844b9134c16c9fe4c8cba6826060 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Thu, 1 Apr 2021 22:59:02 +0000 Subject: [PATCH 166/232] Remove unstable IpPrefix Seems nothing is using this. Bug: 183654927 Test: N/A Change-Id: I94233d79fb5d93970b38a79a09fc4d50822c3620 --- framework/aidl-export/android/net/IpPrefix.aidl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/aidl-export/android/net/IpPrefix.aidl b/framework/aidl-export/android/net/IpPrefix.aidl index 0d70f2a1ed..3495efc1f1 100644 --- a/framework/aidl-export/android/net/IpPrefix.aidl +++ b/framework/aidl-export/android/net/IpPrefix.aidl @@ -18,5 +18,5 @@ package android.net; // @JavaOnlyStableParcelable only affects the parcelable when built as stable aidl (aidl_interface -// build rule). IpPrefix is also used in cpp but only as non-stable aidl. -@JavaOnlyStableParcelable parcelable IpPrefix cpp_header "binder/IpPrefix.h"; +// build rule). +@JavaOnlyStableParcelable parcelable IpPrefix; From 5cec03468c643dbb34f76af0535234f4bdb4458d Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Wed, 24 Mar 2021 14:01:51 -0700 Subject: [PATCH 167/232] Downgrade list of subIds in NetworkCapabilities to @SystemApi This change downgrades API visibility for the list-of-subIds in the NetworkCapabilities to SystemApi Bug: 175662146 Test: atest NetworkCapabilitiesTest#testSubIds Test: atest FrameworksNetTests Change-Id: I372fa9eaa7585aefd1710948ca007456feedd578 --- framework/api/current.txt | 2 -- framework/api/system-current.txt | 2 ++ framework/src/android/net/NetworkCapabilities.java | 13 +++++++++++++ framework/src/android/net/NetworkRequest.java | 5 +++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index 0a9560a5c5..ab290f9d70 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -298,7 +298,6 @@ package android.net { method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); method public int getOwnerUid(); method public int getSignalStrength(); - method @NonNull public java.util.Set getSubIds(); method @Nullable public android.net.TransportInfo getTransportInfo(); method public boolean hasCapability(int); method public boolean hasTransport(int); @@ -408,7 +407,6 @@ package android.net { method public android.net.NetworkRequest.Builder removeTransportType(int); method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); - method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set); } public class ParseException extends java.lang.RuntimeException { diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index afd4fa0de1..251e5e846a 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -274,6 +274,7 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { method @NonNull public int[] getAdministratorUids(); method @Nullable public String getSsid(); + method @NonNull public java.util.Set getSubIds(); method @NonNull public int[] getTransportTypes(); method public boolean isPrivateDnsBroken(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); @@ -327,6 +328,7 @@ package android.net { public static class NetworkRequest.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); + method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set); } public final class NetworkScore implements android.os.Parcelable { diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 7e7089176e..4464b6ebf8 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -2346,9 +2346,15 @@ public final class NetworkCapabilities implements Parcelable { /** * Gets the subscription ID set that associated to this network or request. + * + *

      Instances of NetworkCapabilities will only have this field populated by the system if the + * receiver holds the NETWORK_FACTORY permission. In all other cases, it will be the empty set. + * * @return + * @hide */ @NonNull + @SystemApi public Set getSubIds() { return new ArraySet<>(mSubIds); } @@ -2713,10 +2719,17 @@ public final class NetworkCapabilities implements Parcelable { /** * Set the subscription ID set. * + *

      SubIds are populated in NetworkCapability instances from the system only for callers + * that hold the NETWORK_FACTORY permission. Similarly, the system will reject any + * NetworkRequests filed with a non-empty set of subIds unless the caller holds the + * NETWORK_FACTORY permission. + * * @param subIds a set that represent the subscription IDs. Empty if clean up. * @return this builder. + * @hide */ @NonNull + @SystemApi public Builder setSubIds(@NonNull final Set subIds) { mCaps.setSubIds(subIds); return this; diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 38691ef5cb..5d40417440 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -501,9 +501,14 @@ public class NetworkRequest implements Parcelable { * A network will satisfy this request only if it matches one of the subIds in this set. * An empty set matches all networks, including those without a subId. * + *

      Registering a NetworkRequest with a non-empty set of subIds requires the + * NETWORK_FACTORY permission. + * * @param subIds A {@code Set} that represents subscription IDs. + * @hide */ @NonNull + @SystemApi public Builder setSubIds(@NonNull Set subIds) { mNetworkCapabilities.setSubIds(subIds); return this; From e1ce6ae5d0f87a9f56c32c82994f49ff55fea07a Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Wed, 17 Mar 2021 17:03:34 +0900 Subject: [PATCH 168/232] Cleanup setGlobalProxy This is a small cleanup of a function called by DevicePolicyManager coming from a conflicting change. This no longer makes a concrete difference but is still a good change. Test: m services.devicepolicy Bug: 172183305 Change-Id: I7ee907314ddb253eb4e97d177f0ea0ab3b58cf03 --- .../src/android/net/ConnectivityManager.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index c6f4e0b354..92ed7fc29e 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3140,18 +3140,27 @@ public class ConnectivityManager { } /** - * Set a network-independent global http proxy. This is not normally what you want - * for typical HTTP proxies - they are general network dependent. However if you're - * doing something unusual like general internal filtering this may be useful. On - * a private network where the proxy is not accessible, you may break HTTP using this. + * Set a network-independent global HTTP proxy. * - * @param p A {@link ProxyInfo} object defining the new global - * HTTP proxy. A {@code null} value will clear the global HTTP proxy. + * This sets an HTTP proxy that applies to all networks and overrides any network-specific + * proxy. If set, HTTP libraries that are proxy-aware will use this global proxy when + * accessing any network, regardless of what the settings for that network are. + * + * Note that HTTP proxies are by nature typically network-dependent, and setting a global + * proxy is likely to break networking on multiple networks. This method is only meant + * for device policy clients looking to do general internal filtering or similar use cases. + * + * {@see #getGlobalProxy} + * {@see LinkProperties#getHttpProxy} + * + * @param p A {@link ProxyInfo} object defining the new global HTTP proxy. Calling this + * method with a {@code null} value will clear the global HTTP proxy. * @hide */ + // Used by Device Policy Manager to set the global proxy. @SystemApi(client = MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.NETWORK_STACK) - public void setGlobalProxy(@Nullable ProxyInfo p) { + public void setGlobalProxy(@Nullable final ProxyInfo p) { try { mService.setGlobalProxy(p); } catch (RemoteException e) { From fc3adb5ae1e2ca445aae993780b55fee82338302 Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Mon, 5 Apr 2021 09:23:30 -0700 Subject: [PATCH 169/232] NetworkCapabilities: Hide copy constructor Only used by connectivity service, no need to mark it public. Bug: 184537591 Test: Compiles Change-Id: Ie0d515d73e30a4e15141a3d92aa739192badeb13 --- framework/api/module-lib-current.txt | 1 - framework/src/android/net/NetworkCapabilities.java | 1 - 2 files changed, 2 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index cd96a1b481..82d5d6e238 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -104,7 +104,6 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { - ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, long); method @Nullable public java.util.Set> getUids(); method public boolean hasUnwantedCapability(int); field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index d39f4fb70b..0619bbd9e1 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -163,7 +163,6 @@ public final class NetworkCapabilities implements Parcelable { * {@link NetworkCapabilities}. * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public NetworkCapabilities(@Nullable NetworkCapabilities nc, @RedactionType long redactions) { mRedactions = redactions; if (nc != null) { From acbbb253f4ccf4740b2ce1eb4dc7d9877f8e2e9f Mon Sep 17 00:00:00 2001 From: Ken Chen Date: Fri, 2 Apr 2021 01:44:27 +0800 Subject: [PATCH 170/232] Downgrade DNS fail message from Error to Warning Most DNS fails are caused by network issues, like connection timed out. Lower log severity from error to warning to conform to go/greenlog. Test: 1. connect to a WiFi AP without WAN 2. atest CtsNetTestCases:android.net.cts.DnsResolverTest 3. adb logcat | grep resNetworkResult:android Bug: 181269159 Change-Id: Ifeaf250c91a4b9123d200bd62f085c11009f2491 --- framework/src/android/net/DnsResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/android/net/DnsResolver.java b/framework/src/android/net/DnsResolver.java index 3f7660f570..dac88ad907 100644 --- a/framework/src/android/net/DnsResolver.java +++ b/framework/src/android/net/DnsResolver.java @@ -500,7 +500,7 @@ public final class DnsResolver { try { resp = resNetworkResult(fd); // Closes fd, marks it invalid. } catch (ErrnoException e) { - Log.e(TAG, "resNetworkResult:" + e.toString()); + Log.w(TAG, "resNetworkResult:" + e.toString()); exception = e; } } From 9189c89f52de1f18706674d89be624a2f1f81633 Mon Sep 17 00:00:00 2001 From: James Mattis Date: Thu, 25 Mar 2021 17:17:58 -0700 Subject: [PATCH 171/232] Adding OEM Network Preferences for testing Added two new per-app OEM network preferences to be used only with testing. Although there is a tradeoff adding these preferences to non-test code, the benefit is that it will allow testing of the OEM network preferences app via CTS as using the shell with elevated permissions is not an option due to the OEM network preferences API potentially being abused. On consideration, a small amount of test code was deemed an acceptable tradeoff if it enabled CTS testing. Bug: 176496580 Bug: 176494815 Test: atest FrameworksNetTests atest FrameworksNetIntegrationTests atest CtsNetTestCasesLatestSdk Change-Id: Ie896a7b40945a46a352f37d2c00671da8a8f4489 --- .../android/net/OemNetworkPreferences.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/OemNetworkPreferences.java b/framework/src/android/net/OemNetworkPreferences.java index 5a76cd6d6b..2bb006df82 100644 --- a/framework/src/android/net/OemNetworkPreferences.java +++ b/framework/src/android/net/OemNetworkPreferences.java @@ -40,6 +40,23 @@ import java.util.Objects; */ @SystemApi public final class OemNetworkPreferences implements Parcelable { + // Valid production preferences must be > 0, negative values reserved for testing + /** + * This preference is only to be used for testing and nothing else. + * Use only TRANSPORT_TEST transport networks. + * @hide + */ + public static final int OEM_NETWORK_PREFERENCE_TEST_ONLY = -2; + + /** + * This preference is only to be used for testing and nothing else. + * If an unmetered network is available, use it. + * Otherwise, if a network with the TRANSPORT_TEST transport is available, use it. + * Otherwise, use the general default network. + * @hide + */ + public static final int OEM_NETWORK_PREFERENCE_TEST = -1; + /** * Default in case this value is not set. Using it will result in an error. */ @@ -69,6 +86,12 @@ public final class OemNetworkPreferences implements Parcelable { */ public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; + /** + * The max allowed value for an OEM network preference. + * @hide + */ + public static final int OEM_NETWORK_PREFERENCE_MAX = OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + @NonNull private final Bundle mNetworkMappings; @@ -96,7 +119,7 @@ public final class OemNetworkPreferences implements Parcelable { @Override public String toString() { - return "OemNetworkPreferences{" + "mNetworkMappings=" + mNetworkMappings + '}'; + return "OemNetworkPreferences{" + "mNetworkMappings=" + getNetworkPreferences() + '}'; } @Override @@ -185,6 +208,8 @@ public final class OemNetworkPreferences implements Parcelable { /** @hide */ @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = { + OEM_NETWORK_PREFERENCE_TEST_ONLY, + OEM_NETWORK_PREFERENCE_TEST, OEM_NETWORK_PREFERENCE_UNINITIALIZED, OEM_NETWORK_PREFERENCE_OEM_PAID, OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK, @@ -205,6 +230,10 @@ public final class OemNetworkPreferences implements Parcelable { @NonNull public static String oemNetworkPreferenceToString(@OemNetworkPreference int value) { switch (value) { + case OEM_NETWORK_PREFERENCE_TEST_ONLY: + return "OEM_NETWORK_PREFERENCE_TEST_ONLY"; + case OEM_NETWORK_PREFERENCE_TEST: + return "OEM_NETWORK_PREFERENCE_TEST"; case OEM_NETWORK_PREFERENCE_UNINITIALIZED: return "OEM_NETWORK_PREFERENCE_UNINITIALIZED"; case OEM_NETWORK_PREFERENCE_OEM_PAID: From 189d009e37e85d4d1f0fa3d10023e0f09198b562 Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Thu, 11 Mar 2021 21:16:44 -0800 Subject: [PATCH 172/232] ConnectivityManager: Address review comments from aosp/1595396 Bug: 156867433 Test: atest android.net Test: atest com.android.server Change-Id: I7f5d043732ae22edd14bf581b7dc676c9236b545 --- framework/src/android/net/ConnectivityManager.java | 2 ++ framework/src/android/net/NetworkCapabilities.java | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index d196c1a2d1..68f9fbbd28 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3409,6 +3409,8 @@ public class ConnectivityManager { * not include location sensitive info. *

      */ + // Note: Some existing fields which are location sensitive may still be included without + // this flag if the app targets SDK < S (to maintain backwards compatibility). public static final int FLAG_INCLUDE_LOCATION_INFO = 1 << 0; /** @hide */ diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 881fa8c270..d9acc3178a 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -1141,7 +1141,9 @@ public final class NetworkCapabilities implements Parcelable { * app needs to hold {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission. If the * app targets SDK version greater than or equal to {@link Build.VERSION_CODES#S}, then they * also need to use {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} to get the info in their - * callback. The app will be blamed for location access if this field is included. + * callback. If the apps targets SDK version equal to {{@link Build.VERSION_CODES#R}, this field + * will always be included. The app will be blamed for location access if this field is + * included. *

      */ public int getOwnerUid() { From 2b79e200c339ce1678f78434579a91cb903d34f2 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Tue, 6 Apr 2021 21:15:42 +0800 Subject: [PATCH 173/232] Assign the bit calculation as long to prevent overflow The value should be assigned as a long to do the bit calculation as the mNetworkCapabilities is intended to be a long. Otherwise, the value will be temporary assigned into an integer then assigned to the target long. When the bit shift calculation is out of the integer scope, the calculation will overflow and result in unexpected bebavior. Without assigning to a long, ConnectivityServiceTest will get Out-Of-Memory in StringBuilder while generating toString() in NetworkCapabilities after updating tests to verify NET_CAPABILITY_VSIM and NET_CAPABILITY_BIP. Bug: 130869457 Test: atest FrameworksNetTests Change-Id: I4d34c1215c7efb6dc352c314107792e3fa512ad7 --- .../src/android/net/NetworkCapabilities.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index d39f4fb70b..ac934569c1 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -593,8 +593,9 @@ public final class NetworkCapabilities implements Parcelable { // TODO: Consider adding unwanted capabilities to the public API and mention this // in the documentation. checkValidCapability(capability); - mNetworkCapabilities |= 1 << capability; - mUnwantedNetworkCapabilities &= ~(1 << capability); // remove from unwanted capability list + mNetworkCapabilities |= 1L << capability; + // remove from unwanted capability list + mUnwantedNetworkCapabilities &= ~(1L << capability); return this; } @@ -613,8 +614,8 @@ public final class NetworkCapabilities implements Parcelable { */ public void addUnwantedCapability(@NetCapability int capability) { checkValidCapability(capability); - mUnwantedNetworkCapabilities |= 1 << capability; - mNetworkCapabilities &= ~(1 << capability); // remove from requested capabilities + mUnwantedNetworkCapabilities |= 1L << capability; + mNetworkCapabilities &= ~(1L << capability); // remove from requested capabilities } /** @@ -627,7 +628,7 @@ public final class NetworkCapabilities implements Parcelable { */ public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) { checkValidCapability(capability); - final long mask = ~(1 << capability); + final long mask = ~(1L << capability); mNetworkCapabilities &= mask; return this; } @@ -642,7 +643,7 @@ public final class NetworkCapabilities implements Parcelable { */ public @NonNull NetworkCapabilities removeUnwantedCapability(@NetCapability int capability) { checkValidCapability(capability); - mUnwantedNetworkCapabilities &= ~(1 << capability); + mUnwantedNetworkCapabilities &= ~(1L << capability); return this; } @@ -710,14 +711,14 @@ public final class NetworkCapabilities implements Parcelable { */ public boolean hasCapability(@NetCapability int capability) { return isValidCapability(capability) - && ((mNetworkCapabilities & (1 << capability)) != 0); + && ((mNetworkCapabilities & (1L << capability)) != 0); } /** @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public boolean hasUnwantedCapability(@NetCapability int capability) { return isValidCapability(capability) - && ((mUnwantedNetworkCapabilities & (1 << capability)) != 0); + && ((mUnwantedNetworkCapabilities & (1L << capability)) != 0); } /** From a35f6c23d87d0d60b4fdd2a47c42cddb719def4b Mon Sep 17 00:00:00 2001 From: Aaron Huang Date: Tue, 23 Mar 2021 14:54:53 +0800 Subject: [PATCH 174/232] Remove Network, NetworkRequest metrics from jobscheduler These metrics are deprecated so remove them from jobscheduler. Also remove dumpDebug method from Network, NetworkRequest and NetworkCapabilities because there's no caller anymore. This change also for connectivity mainline module. These three classes are inculded in framework-connectivity so external module cannot have dependencies on its hidden API. With this change, the dependencies can be removed. (cherry-picked from ag/13959431) Bug: 178777253 Test: FrameworksNetTests JobStoreTest adb shell dumpsys jobscheduler --proto CtsIncidentHostTestCases:JobSchedulerIncidentTest Merged-In: Ie0c540303ba06b8fba029d2b98ae753afb08c963 Change-Id: Ie0c540303ba06b8fba029d2b98ae753afb08c963 --- framework/src/android/net/Network.java | 8 ----- .../src/android/net/NetworkCapabilities.java | 29 ------------------- framework/src/android/net/NetworkRequest.java | 13 --------- 3 files changed, 50 deletions(-) diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java index 0741414ab3..41fad6317a 100644 --- a/framework/src/android/net/Network.java +++ b/framework/src/android/net/Network.java @@ -27,7 +27,6 @@ import android.os.Parcelable; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; @@ -526,11 +525,4 @@ public class Network implements Parcelable { public String toString() { return Integer.toString(netId); } - - /** @hide */ - public void dumpDebug(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - proto.write(NetworkProto.NET_ID, netId); - proto.end(token); - } } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index d39f4fb70b..3b549cc608 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -35,7 +35,6 @@ import android.os.Process; import android.text.TextUtils; import android.util.ArraySet; import android.util.Range; -import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.net.module.util.CollectionUtils; @@ -2058,34 +2057,6 @@ public final class NetworkCapabilities implements Parcelable { } } - /** @hide */ - public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - - for (int transport : getTransportTypes()) { - proto.write(NetworkCapabilitiesProto.TRANSPORTS, transport); - } - - for (int capability : getCapabilities()) { - proto.write(NetworkCapabilitiesProto.CAPABILITIES, capability); - } - - proto.write(NetworkCapabilitiesProto.LINK_UP_BANDWIDTH_KBPS, mLinkUpBandwidthKbps); - proto.write(NetworkCapabilitiesProto.LINK_DOWN_BANDWIDTH_KBPS, mLinkDownBandwidthKbps); - - if (mNetworkSpecifier != null) { - proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString()); - } - if (mTransportInfo != null) { - // TODO b/120653863: write transport-specific info to proto? - } - - proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength()); - proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength); - - proto.end(token); - } - /** * @hide */ diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 5d40417440..3a8a07a55a 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -47,7 +47,6 @@ import android.os.Parcelable; import android.os.Process; import android.text.TextUtils; import android.util.Range; -import android.util.proto.ProtoOutputStream; import java.util.Arrays; import java.util.List; @@ -680,18 +679,6 @@ public class NetworkRequest implements Parcelable { } } - /** @hide */ - public void dumpDebug(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - - proto.write(NetworkRequestProto.TYPE, typeToProtoEnum(type)); - proto.write(NetworkRequestProto.REQUEST_ID, requestId); - proto.write(NetworkRequestProto.LEGACY_TYPE, legacyType); - networkCapabilities.dumpDebug(proto, NetworkRequestProto.NETWORK_CAPABILITIES); - - proto.end(token); - } - public boolean equals(@Nullable Object obj) { if (obj instanceof NetworkRequest == false) return false; NetworkRequest that = (NetworkRequest)obj; From d89b2980df57bc5a76458c389a78a2f27043c4a1 Mon Sep 17 00:00:00 2001 From: lucaslin Date: Thu, 4 Mar 2021 17:09:51 +0800 Subject: [PATCH 175/232] Expose constants of ConnectivityManager The callers cannot call the hidden APIs after ConnectivityManager became a part of mainline module, so expose them for callers. Also change the value of ACTION_PROMPT_PARTIAL_CONNECTIVITY, ACTION_PROMPT_LOST_VALIDATION and ACTION_PROMPT_UNVALIDATED because of API lint errors. Bug: 172183305 Test: Check private DNS settings is normal, and test NO_INTERNET notification can be shown normally. Change-Id: I715c766ad8e5eb54f4dc67239c1dbca7239506fc Merged-In: I715c766ad8e5eb54f4dc67239c1dbca7239506fc --- framework/api/module-lib-current.txt | 3 +++ framework/src/android/net/ConnectivityManager.java | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index f2f5c1b7f7..2065559227 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -26,6 +26,9 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network); method public void systemReady(); + field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION"; + field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY"; + field public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED"; field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000 field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000 field public static final int BLOCKED_METERED_REASON_MASK = -65536; // 0xffff0000 diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 92ed7fc29e..be1b6a24a4 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -425,7 +425,8 @@ public class ConnectivityManager { * * @hide */ - public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED"; + @SystemApi(client = MODULE_LIBRARIES) + public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED"; /** * Action used to display a dialog that asks the user whether to avoid a network that is no @@ -433,8 +434,9 @@ public class ConnectivityManager { * * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public static final String ACTION_PROMPT_LOST_VALIDATION = - "android.net.conn.PROMPT_LOST_VALIDATION"; + "android.net.action.PROMPT_LOST_VALIDATION"; /** * Action used to display a dialog that asks the user whether to stay connected to a network @@ -443,8 +445,9 @@ public class ConnectivityManager { * * @hide */ + @SystemApi(client = MODULE_LIBRARIES) public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = - "android.net.conn.PROMPT_PARTIAL_CONNECTIVITY"; + "android.net.action.PROMPT_PARTIAL_CONNECTIVITY"; /** * Invalid tethering type. From b49c8425b247ad4b5962756baf3b49fbc355a2bf Mon Sep 17 00:00:00 2001 From: paulhu Date: Wed, 7 Apr 2021 16:18:13 +0800 Subject: [PATCH 176/232] Move ACTION_CLEAR_DNS_CACHE to ConnectivityManager - As API review feedback, move ACTION_CLEAR_DNS_CACHE form Intent to ConnectivityManager. - Rename to "android.net.action.CLEAR_DNS_CACHE" because of lint suggestion. frameworks/base/packages/Connectivity/framework/src/android/net/ ConnectivityManager.java:449: error: Inconsistent action value; expected `android.net.action.CLEAR_DNS_CACHE`, was `android.intent.action.CLEAR_DNS_CACHE` [ActionValue] Bug: 183937999 Test: atest FrameworksNetTests Test: atest ActivityTaskManagerServiceTests Test: atest android.permission2.cts.PermissionPolicyTest Test: atest CtsNetTestCases Change-Id: Iae8aa0ba10dfc7581f0cfaab82643edbee789e2f --- framework/api/module-lib-current.txt | 1 + framework/src/android/net/ConnectivityManager.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 513b6308f3..8dfdd6145d 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -26,6 +26,7 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network); method public void systemReady(); + field public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE"; field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION"; field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY"; field public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED"; diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 84914a1aee..9fbf04952f 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -449,6 +449,15 @@ public class ConnectivityManager { public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY"; + /** + * Clear DNS Cache Action: This is broadcast when networks have changed and old + * DNS entries should be cleared. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE"; + /** * Invalid tethering type. * @see #startTethering(int, boolean, OnStartTetheringCallback) From 019b0eef3c5324595e9a763749e9665422f61443 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 8 Apr 2021 13:59:10 +0900 Subject: [PATCH 177/232] Replace fields with getters in keepalive API General guidance is to have getters in the API instead of fields. Fixes: 181014882 Test: m Change-Id: Id4bfc447701e8d0380163047779fbba043f17b6f --- framework/api/system-current.txt | 12 +-- .../android/net/TcpKeepalivePacketData.java | 79 +++++++++++++++++-- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 0a82cb7cfe..d6d38889e9 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -455,14 +455,14 @@ package android.net { public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException; method public int describeContents(); + method public int getIpTos(); + method public int getIpTtl(); + method public int getTcpAck(); + method public int getTcpSeq(); + method public int getTcpWindow(); + method public int getTcpWindowScale(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public final int ipTos; - field public final int ipTtl; - field public final int tcpAck; - field public final int tcpSeq; - field public final int tcpWindow; - field public final int tcpWindowScale; } } diff --git a/framework/src/android/net/TcpKeepalivePacketData.java b/framework/src/android/net/TcpKeepalivePacketData.java index ddb3a6a72f..c2c4f32ca6 100644 --- a/framework/src/android/net/TcpKeepalivePacketData.java +++ b/framework/src/android/net/TcpKeepalivePacketData.java @@ -32,22 +32,39 @@ import java.util.Objects; public final class TcpKeepalivePacketData extends KeepalivePacketData implements Parcelable { private static final String TAG = "TcpKeepalivePacketData"; - /** TCP sequence number. */ + /** + * TCP sequence number. + * @hide + */ public final int tcpSeq; - /** TCP ACK number. */ + /** + * TCP ACK number. + * @hide + */ public final int tcpAck; - /** TCP RCV window. */ + /** + * TCP RCV window. + * @hide + */ public final int tcpWindow; - /** TCP RCV window scale. */ + /** TCP RCV window scale. + * @hide + */ public final int tcpWindowScale; - /** IP TOS. */ + /** + * IP TOS. + * @hide + */ public final int ipTos; - /** IP TTL. */ + /** + * IP TTL. + * @hide + */ public final int ipTtl; public TcpKeepalivePacketData(@NonNull final InetAddress srcAddress, int srcPort, @@ -63,6 +80,56 @@ public final class TcpKeepalivePacketData extends KeepalivePacketData implements this.ipTtl = ipTtl; } + /** + * Get the TCP sequence number. + * + * See https://tools.ietf.org/html/rfc793#page-15. + */ + public int getTcpSeq() { + return tcpSeq; + } + + /** + * Get the TCP ACK number. + * + * See https://tools.ietf.org/html/rfc793#page-15. + */ + public int getTcpAck() { + return tcpAck; + } + + /** + * Get the TCP RCV window. + * + * See https://tools.ietf.org/html/rfc793#page-15. + */ + public int getTcpWindow() { + return tcpWindow; + } + + /** + * Get the TCP RCV window scale. + * + * See https://tools.ietf.org/html/rfc793#page-15. + */ + public int getTcpWindowScale() { + return tcpWindowScale; + } + + /** + * Get the IP type of service. + */ + public int getIpTos() { + return ipTos; + } + + /** + * Get the IP TTL. + */ + public int getIpTtl() { + return ipTtl; + } + @Override public boolean equals(@Nullable final Object o) { if (!(o instanceof TcpKeepalivePacketData)) return false; From 84a217a6ef13761bd8129ebf7f564a6730f4e59d Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 8 Apr 2021 15:56:28 +0900 Subject: [PATCH 178/232] Set ParseException constructors as public As there is no strong reason to keep the constructors module-lib, set them as public API. This is in response to API feedback. Fixes: 183446251 Test: m Change-Id: I01daa6f6f8095f7a4db94d1ca05f913166939df3 --- framework/api/current.txt | 2 ++ framework/api/module-lib-current.txt | 5 ----- framework/src/android/net/ParseException.java | 5 ----- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/framework/api/current.txt b/framework/api/current.txt index ab290f9d70..7692e30c31 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -410,6 +410,8 @@ package android.net { } public class ParseException extends java.lang.RuntimeException { + ctor public ParseException(@NonNull String); + ctor public ParseException(@NonNull String, @NonNull Throwable); field public String response; } diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 513b6308f3..5cf12b26dd 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -132,11 +132,6 @@ package android.net { method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set>); } - public class ParseException extends java.lang.RuntimeException { - ctor public ParseException(@NonNull String); - ctor public ParseException(@NonNull String, @NonNull Throwable); - } - public final class TcpRepairWindow { ctor public TcpRepairWindow(int, int, int, int, int, int); field public final int maxWindow; diff --git a/framework/src/android/net/ParseException.java b/framework/src/android/net/ParseException.java index ca6d012dfe..9d4727a84b 100644 --- a/framework/src/android/net/ParseException.java +++ b/framework/src/android/net/ParseException.java @@ -17,7 +17,6 @@ package android.net; import android.annotation.NonNull; -import android.annotation.SystemApi; /** * Thrown when parsing failed. @@ -26,15 +25,11 @@ import android.annotation.SystemApi; public class ParseException extends RuntimeException { public String response; - /** @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public ParseException(@NonNull String response) { super(response); this.response = response; } - /** @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public ParseException(@NonNull String response, @NonNull Throwable cause) { super(response, cause); this.response = response; From c5174c3c10de96b882eb1b2807049b3ebaf7141a Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 8 Apr 2021 16:08:46 +0900 Subject: [PATCH 179/232] Add RequiresPermission to TestNetworkManager All API methods in TestNetworkManager require the MANAGE_TEST_NETWORKS permission. Fixes: 183972672 Test: m Change-Id: Ic5929c24ea88d7259d367a81fec8f223a2e3ecb0 --- framework/api/module-lib-current.txt | 8 ++++---- framework/src/android/net/TestNetworkManager.java | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 513b6308f3..2085880f89 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -157,10 +157,10 @@ package android.net { } public class TestNetworkManager { - method @NonNull public android.net.TestNetworkInterface createTapInterface(); - method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection); - method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); - method public void teardownTestNetwork(@NonNull android.net.Network); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public android.net.TestNetworkInterface createTapInterface(); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection); + method @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); + method @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public void teardownTestNetwork(@NonNull android.net.Network); field public static final String TEST_TAP_PREFIX = "testtap"; } diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java index a7a62351e5..9ddd2f5767 100644 --- a/framework/src/android/net/TestNetworkManager.java +++ b/framework/src/android/net/TestNetworkManager.java @@ -15,8 +15,10 @@ */ package android.net; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.os.IBinder; import android.os.RemoteException; @@ -58,6 +60,7 @@ public class TestNetworkManager { * @param network The test network that should be torn down * @hide */ + @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void teardownTestNetwork(@NonNull Network network) { try { @@ -103,6 +106,7 @@ public class TestNetworkManager { * @param binder A binder object guarding the lifecycle of this test network. * @hide */ + @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) { setupTestNetwork(iface, null, true, new int[0], binder); @@ -145,6 +149,7 @@ public class TestNetworkManager { * TUN interface. * @hide */ + @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull public TestNetworkInterface createTunInterface(@NonNull Collection linkAddrs) { @@ -163,6 +168,7 @@ public class TestNetworkManager { * TAP interface. * @hide */ + @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @NonNull public TestNetworkInterface createTapInterface() { From 564f7f8df3e58cd2c34ff77207b186ce2100cb9d Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 8 Apr 2021 16:26:20 +0900 Subject: [PATCH 180/232] Improve documentation on simulateDataStall Explain that: - The method does not cause an actual data stall. - The detectionMethod parameter refers to ConnectivityDiagnosticsManager.DataStallReport constants (which already use an IntDef). - timestampMillis is relative to SystemClock.elapsedRealtime. Fixes: 183972768 Test: m Change-Id: I2eeb79e0e8ec6e8fd3b8fe1dcb1abbf2e6338983 --- framework/src/android/net/ConnectivityManager.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 84914a1aee..80bbf0ccf2 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -44,6 +44,7 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityDiagnosticsManager.DataStallReport.DetectionMethod; import android.net.IpSecManager.UdpEncapsulationSocket; import android.net.SocketKeepalive.Callback; import android.net.TetheringManager.StartTetheringCallback; @@ -5095,10 +5096,13 @@ public class ConnectivityManager { * *

      This method should only be used for tests. * - *

      The caller must be the owner of the specified Network. + *

      The caller must be the owner of the specified Network. This simulates a data stall to + * have the system behave as if it had happened, but does not actually stall connectivity. * * @param detectionMethod The detection method used to identify the Data Stall. - * @param timestampMillis The timestamp at which the stall 'occurred', in milliseconds. + * See ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_*. + * @param timestampMillis The timestamp at which the stall 'occurred', in milliseconds, as per + * SystemClock.elapsedRealtime. * @param network The Network for which a Data Stall is being simluated. * @param extras The PersistableBundle of extras included in the Data Stall notification. * @throws SecurityException if the caller is not the owner of the given network. @@ -5107,7 +5111,7 @@ public class ConnectivityManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) - public void simulateDataStall(int detectionMethod, long timestampMillis, + public void simulateDataStall(@DetectionMethod int detectionMethod, long timestampMillis, @NonNull Network network, @NonNull PersistableBundle extras) { try { mService.simulateDataStall(detectionMethod, timestampMillis, network, extras); From 495d9ba693c543cf6b3dc75ea3bb50f4c81261e2 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 19 Mar 2021 17:18:01 +0900 Subject: [PATCH 181/232] Use String in CaptivePortalData underlying types The venue friendly name comes from the network, and only plain strings should be used in practice. writeCharSequence is also not usable in CaptivePortalData, as it must build using module_api. Use a String as the underlying type in CaptivePortalData, keeping CharSequence in the API for consistency with UI-related elements. This brings CaptivePortalData in line with downstream branches. Bug: 183174863 Test: atest FrameworksNetTests Merged-In: Ifa3ea9aae0cabf5757791c4ae13e6f551759ed97 Change-Id: Ic662d3b546d52f825f9c3a24c5e0d4ba03818ab0 --- framework/src/android/net/CaptivePortalData.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/android/net/CaptivePortalData.java b/framework/src/android/net/CaptivePortalData.java index 82dbd0fb1f..53aa1b92ed 100644 --- a/framework/src/android/net/CaptivePortalData.java +++ b/framework/src/android/net/CaptivePortalData.java @@ -42,7 +42,7 @@ public final class CaptivePortalData implements Parcelable { private final long mByteLimit; private final long mExpiryTimeMillis; private final boolean mCaptive; - private final CharSequence mVenueFriendlyName; + private final String mVenueFriendlyName; private final int mVenueInfoUrlSource; private final int mUserPortalUrlSource; @@ -73,14 +73,14 @@ public final class CaptivePortalData implements Parcelable { mByteLimit = byteLimit; mExpiryTimeMillis = expiryTimeMillis; mCaptive = captive; - mVenueFriendlyName = venueFriendlyName; + mVenueFriendlyName = venueFriendlyName == null ? null : venueFriendlyName.toString(); mVenueInfoUrlSource = venueInfoUrlSource; mUserPortalUrlSource = userPortalUrlSource; } private CaptivePortalData(Parcel p) { this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(), - p.readLong(), p.readLong(), p.readBoolean(), p.readCharSequence(), p.readInt(), + p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(), p.readInt()); } @@ -98,7 +98,7 @@ public final class CaptivePortalData implements Parcelable { dest.writeLong(mByteLimit); dest.writeLong(mExpiryTimeMillis); dest.writeBoolean(mCaptive); - dest.writeCharSequence(mVenueFriendlyName); + dest.writeString(mVenueFriendlyName); dest.writeInt(mVenueInfoUrlSource); dest.writeInt(mUserPortalUrlSource); } From 1091a06338a107c394cf9f639d59951108556d5c Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Thu, 8 Apr 2021 11:10:51 +0800 Subject: [PATCH 182/232] Make getUid a method on UserHandle itself instead of static This commit addresses the API review feedback that getUid() will be better to make it be a method on UserHandle itself rather than a static method. Update as it is and update the corresponding usages. Fix: 184735865 Test: make update-api Test: atest FrameworksNetTests Test: atest CtsNetTestCasesLatestSdk Change-Id: I33844309224d84764704255d251fadc8940202ca --- framework/src/android/net/UidRange.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/UidRange.java b/framework/src/android/net/UidRange.java index bc67c745c9..bd332928f4 100644 --- a/framework/src/android/net/UidRange.java +++ b/framework/src/android/net/UidRange.java @@ -46,8 +46,8 @@ public final class UidRange implements Parcelable { /** Creates a UidRange for the specified user. */ public static UidRange createForUser(UserHandle user) { final UserHandle nextUser = UserHandle.of(user.getIdentifier() + 1); - final int start = UserHandle.getUid(user, 0 /* appId */); - final int end = UserHandle.getUid(nextUser, 0) - 1; + final int start = user.getUid(0 /* appId */); + final int end = nextUser.getUid(0 /* appId */) - 1; return new UidRange(start, end); } From c3663377e7f10ecef2d2efac097be96843ce2a79 Mon Sep 17 00:00:00 2001 From: paulhu Date: Mon, 12 Apr 2021 10:25:55 +0800 Subject: [PATCH 183/232] Remove TcpRepairWindow from module-lib API Both TcpRepairWindow and TcpKeepaliveController(user) are in the connectivity module, so TcpRepairWindow doesn't need to be the module-lib API. Thus, remove TcpRepairWindow from module-lib API. Bug: 172183305 Test: m update-api Change-Id: I8fde726f8ad73637e6deab69ea83b3699bb2cf45 --- framework/api/module-lib-current.txt | 10 ---------- framework/src/android/net/TcpRepairWindow.java | 3 --- 2 files changed, 13 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index caf7f49efc..4719772075 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -133,16 +133,6 @@ package android.net { method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set>); } - public final class TcpRepairWindow { - ctor public TcpRepairWindow(int, int, int, int, int, int); - field public final int maxWindow; - field public final int rcvWnd; - field public final int rcvWndScale; - field public final int rcvWup; - field public final int sndWl1; - field public final int sndWnd; - } - public final class TestNetworkInterface implements android.os.Parcelable { ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String); method public int describeContents(); diff --git a/framework/src/android/net/TcpRepairWindow.java b/framework/src/android/net/TcpRepairWindow.java index f062fa9034..86034f0a76 100644 --- a/framework/src/android/net/TcpRepairWindow.java +++ b/framework/src/android/net/TcpRepairWindow.java @@ -16,15 +16,12 @@ package android.net; -import android.annotation.SystemApi; - /** * Corresponds to C's {@code struct tcp_repair_window} from * include/uapi/linux/tcp.h * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TcpRepairWindow { public final int sndWl1; public final int sndWnd; From f5324d7d03fba3ad9a17e09b00b3c3dcae515fd5 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Thu, 8 Apr 2021 12:56:51 +0800 Subject: [PATCH 184/232] Rename APIs in NetworkAgentConfig.Builder As API review feedback, rename disableProvisioningNotification() to setEnabledProvisioningNotification and disableNat64Detection() to setEnabledNat64Detection. Also, update code in caller side accordingly. Bug: 184735772 Test: make update-api ; atest FrameworksNetTests Change-Id: If7305634863d1503c967e5593ebd0c8af2174bea --- framework/api/system-current.txt | 4 ++-- .../src/android/net/NetworkAgentConfig.java | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index d6d38889e9..de673ee971 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -259,15 +259,15 @@ package android.net { public static final class NetworkAgentConfig.Builder { ctor public NetworkAgentConfig.Builder(); method @NonNull public android.net.NetworkAgentConfig build(); - method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection(); - method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification(); method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); + method @NonNull public android.net.NetworkAgentConfig.Builder setNat64DetectionEnabled(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); + method @NonNull public android.net.NetworkAgentConfig.Builder setProvisioningNotificationEnabled(boolean); method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); } diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java index 3f058d8cbf..ad8396b06d 100644 --- a/framework/src/android/net/NetworkAgentConfig.java +++ b/framework/src/android/net/NetworkAgentConfig.java @@ -311,26 +311,28 @@ public final class NetworkAgentConfig implements Parcelable { } /** - * Disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to save power - * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64. + * Enables or disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to + * save power and reduce idle traffic on networks that are known to be IPv6-only without a + * NAT64. By default, NAT64 detection is enabled. * * @return this builder, to facilitate chaining. */ @NonNull - public Builder disableNat64Detection() { - mConfig.skip464xlat = true; + public Builder setNat64DetectionEnabled(boolean enabled) { + mConfig.skip464xlat = !enabled; return this; } /** - * Disables the "Sign in to network" notification. Used if the network transport will - * perform its own carrier-specific provisioning procedure. + * Enables or disables the "Sign in to network" notification. Used if the network transport + * will perform its own carrier-specific provisioning procedure. By default, the + * notification is enabled. * * @return this builder, to facilitate chaining. */ @NonNull - public Builder disableProvisioningNotification() { - mConfig.provisioningNotificationDisabled = true; + public Builder setProvisioningNotificationEnabled(boolean enabled) { + mConfig.provisioningNotificationDisabled = !enabled; return this; } From a34d4892a4d7b27666e851efa3806c26af12eb3e Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Wed, 14 Apr 2021 00:28:19 +0900 Subject: [PATCH 185/232] Add @RedactionType annotations to VpnTransportInfo. This is in response to API council feedback. Test: m Bug: 185226718 Change-Id: I63d7249e7d14ac00558f311a3583f6adbf80e3d9 --- framework/src/android/net/VpnTransportInfo.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java index ba83a44d0d..efd3363771 100644 --- a/framework/src/android/net/VpnTransportInfo.java +++ b/framework/src/android/net/VpnTransportInfo.java @@ -22,6 +22,7 @@ import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.net.NetworkCapabilities.RedactionType; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -45,7 +46,7 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { public final String sessionId; @Override - public long getApplicableRedactions() { + public @RedactionType long getApplicableRedactions() { return REDACT_FOR_NETWORK_SETTINGS; } @@ -53,7 +54,7 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { * Create a copy of a {@link VpnTransportInfo} with the sessionId redacted if necessary. */ @NonNull - public VpnTransportInfo makeCopy(long redactions) { + public VpnTransportInfo makeCopy(@RedactionType long redactions) { return new VpnTransportInfo(type, ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : sessionId); } From 7ceeabe0daf6eaf8091f65e639496d24346e9425 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Thu, 15 Apr 2021 16:04:00 +0800 Subject: [PATCH 186/232] Remove hidden method link in public addRoute API The build will complain after moving connectivity framework code outside framework because the hidden methods/members accesses are not allowed anymore. Link to a hidden class will not work since it's not visible in public. Bug: 182859030 Test: make docs Change-Id: I5726f80be7cf92b648ce851d9601d5f58bc2b647 --- framework/src/android/net/LinkProperties.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java index e41ed72b25..99f48b49c6 100644 --- a/framework/src/android/net/LinkProperties.java +++ b/framework/src/android/net/LinkProperties.java @@ -686,8 +686,8 @@ public final class LinkProperties implements Parcelable { } /** - * Adds a {@link RouteInfo} to this {@code LinkProperties}, if a {@link RouteInfo} - * with the same {@link RouteInfo.RouteKey} with different properties + * Adds a {@link RouteInfo} to this {@code LinkProperties}. If there is a {@link RouteInfo} + * with the same destination, gateway and interface with different properties * (e.g., different MTU), it will be updated. If the {@link RouteInfo} had an * interface name set and that differs from the interface set for this * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown. From 12bcbe5efab2f07fb946c5c46f89d2a406c9b680 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Thu, 15 Apr 2021 18:03:54 +0900 Subject: [PATCH 187/232] Don't expose raw IBinder APIs. APIs should not expose raw IBinder objects. Fix: 184735751 Test: builds, boots Test: atest CtsNetTestCases:android.net.cts.ConnectivityManagerTest Test: atest CtsNetTestCases:android.net.cts.DnsResolverTest Change-Id: Ia0c4170def31123f0b79318fec2cfe02e4fcd3bf --- .../net/ConnectivityFrameworkInitializer.java | 6 +++ .../net/DnsResolverServiceManager.java | 45 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 framework/src/android/net/DnsResolverServiceManager.java diff --git a/framework/src/android/net/ConnectivityFrameworkInitializer.java b/framework/src/android/net/ConnectivityFrameworkInitializer.java index 92a792b784..a2e218dcbb 100644 --- a/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -68,5 +68,11 @@ public final class ConnectivityFrameworkInitializer { return cm.startOrGetTestNetworkManager(); } ); + + SystemServiceRegistry.registerContextAwareService( + DnsResolverServiceManager.DNS_RESOLVER_SERVICE, + DnsResolverServiceManager.class, + (context, serviceBinder) -> new DnsResolverServiceManager(serviceBinder) + ); } } diff --git a/framework/src/android/net/DnsResolverServiceManager.java b/framework/src/android/net/DnsResolverServiceManager.java new file mode 100644 index 0000000000..79009e8d62 --- /dev/null +++ b/framework/src/android/net/DnsResolverServiceManager.java @@ -0,0 +1,45 @@ +/* + * 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 android.net; + +import android.annotation.NonNull; +import android.os.IBinder; + +/** + * Provides a way to obtain the DnsResolver binder objects. + * + * @hide + */ +public class DnsResolverServiceManager { + /** Service name for the DNS resolver. Keep in sync with DnsResolverService.h */ + public static final String DNS_RESOLVER_SERVICE = "dnsresolver"; + + private final IBinder mResolver; + + DnsResolverServiceManager(IBinder resolver) { + mResolver = resolver; + } + + /** + * Get an {@link IBinder} representing the DnsResolver stable AIDL interface + * + * @return {@link android.net.IDnsResolver} IBinder. + */ + @NonNull + public IBinder getService() { + return mResolver; + } +} From 8c8ac36d9afb0ed89971074402c29ff16d6ddab4 Mon Sep 17 00:00:00 2001 From: junyulai Date: Thu, 15 Apr 2021 00:39:49 +0800 Subject: [PATCH 188/232] [VCN20] Change requestBackgroundNetwork argument order Test: atest FrameworksNetTests FrameworksVcnTests Fix: 185215095 Merged-In: Id281678fe85ce0894b0e92e11c0283d4d1b4ecdb Change-Id: Id281678fe85ce0894b0e92e11c0283d4d1b4ecdb (cherry-picked from ag/14198667) --- framework/api/module-lib-current.txt | 2 +- framework/src/android/net/ConnectivityManager.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 4719772075..9e2cd3e8a7 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -13,7 +13,7 @@ package android.net { method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 0418450ccd..043ff383e7 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -5307,10 +5307,10 @@ public class ConnectivityManager { * {@link #unregisterNetworkCallback(NetworkCallback)}. * * @param request {@link NetworkRequest} describing this request. - * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. - * If null, the callback is invoked on the default internal Handler. * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note * the callback must not be shared - it uniquely specifies this request. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * If null, the callback is invoked on the default internal Handler. * @throws IllegalArgumentException if {@code request} contains invalid network capabilities. * @throws SecurityException if missing the appropriate permissions. * @throws RuntimeException if the app already has too many callbacks registered. @@ -5325,7 +5325,8 @@ public class ConnectivityManager { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK }) public void requestBackgroundNetwork(@NonNull NetworkRequest request, - @NonNull Handler handler, @NonNull NetworkCallback networkCallback) { + @NonNull NetworkCallback networkCallback, + @SuppressLint("ListenerLast") @NonNull Handler handler) { final NetworkCapabilities nc = request.networkCapabilities; sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, TYPE_NONE, new CallbackHandler(handler)); From 651928c3109f5cba9ceba541e9391064907d5a27 Mon Sep 17 00:00:00 2001 From: junyulai Date: Wed, 14 Apr 2021 23:33:31 +0800 Subject: [PATCH 189/232] [VCN19] Rename get/setSubIds to get/setSubscriptionIds Test: atest FrameworksNetTests FrameworksVcnTests Fix: 185215036 Merged-In: I9d90df5fc13b36d2cdc4920b456dcc87fcd2b3a7 Change-Id: I9d90df5fc13b36d2cdc4920b456dcc87fcd2b3a7 (cherry-picked from ag/14198665) --- framework/api/system-current.txt | 6 +++--- .../src/android/net/NetworkCapabilities.java | 20 +++++++++---------- framework/src/android/net/NetworkRequest.java | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index de673ee971..935b09330c 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -275,7 +275,7 @@ package android.net { method @NonNull public int[] getAdministratorUids(); method @Nullable public static String getCapabilityCarrierName(int); method @Nullable public String getSsid(); - method @NonNull public java.util.Set getSubIds(); + method @NonNull public java.util.Set getSubscriptionIds(); method @NonNull public int[] getTransportTypes(); method public boolean isPrivateDnsBroken(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); @@ -306,7 +306,7 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); - method @NonNull public android.net.NetworkCapabilities.Builder setSubIds(@NonNull java.util.Set); + method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set); method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); } @@ -329,7 +329,7 @@ package android.net { public static class NetworkRequest.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); - method @NonNull public android.net.NetworkRequest.Builder setSubIds(@NonNull java.util.Set); + method @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set); } public final class NetworkScore implements android.os.Parcelable { diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index a43dd15a87..937a9d2393 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -1733,7 +1733,7 @@ public final class NetworkCapabilities implements Parcelable { combineSSIDs(nc); combineRequestor(nc); combineAdministratorUids(nc); - combineSubIds(nc); + combineSubscriptionIds(nc); } /** @@ -1755,7 +1755,7 @@ public final class NetworkCapabilities implements Parcelable { && (onlyImmutable || satisfiedByUids(nc)) && (onlyImmutable || satisfiedBySSID(nc)) && (onlyImmutable || satisfiedByRequestor(nc)) - && (onlyImmutable || satisfiedBySubIds(nc))); + && (onlyImmutable || satisfiedBySubscriptionIds(nc))); } /** @@ -1852,7 +1852,7 @@ public final class NetworkCapabilities implements Parcelable { && equalsPrivateDnsBroken(that) && equalsRequestor(that) && equalsAdministratorUids(that) - && equalsSubIds(that); + && equalsSubscriptionIds(that); } @Override @@ -2329,7 +2329,7 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ @NonNull - public NetworkCapabilities setSubIds(@NonNull Set subIds) { + public NetworkCapabilities setSubscriptionIds(@NonNull Set subIds) { mSubIds = new ArraySet(Objects.requireNonNull(subIds)); return this; } @@ -2345,14 +2345,14 @@ public final class NetworkCapabilities implements Parcelable { */ @NonNull @SystemApi - public Set getSubIds() { + public Set getSubscriptionIds() { return new ArraySet<>(mSubIds); } /** * Tests if the subscription ID set of this network is the same as that of the passed one. */ - private boolean equalsSubIds(@NonNull NetworkCapabilities nc) { + private boolean equalsSubscriptionIds(@NonNull NetworkCapabilities nc) { return Objects.equals(mSubIds, nc.mSubIds); } @@ -2361,7 +2361,7 @@ public final class NetworkCapabilities implements Parcelable { * If specified in the request, the passed one need to have at least one subId and at least * one of them needs to be in the request set. */ - private boolean satisfiedBySubIds(@NonNull NetworkCapabilities nc) { + private boolean satisfiedBySubscriptionIds(@NonNull NetworkCapabilities nc) { if (mSubIds.isEmpty()) return true; if (nc.mSubIds.isEmpty()) return false; for (final Integer subId : nc.mSubIds) { @@ -2378,7 +2378,7 @@ public final class NetworkCapabilities implements Parcelable { *

      If both subscription IDs are not equal, they belong to different subscription * (or no subscription). In this case, it would not make sense to add them together. */ - private void combineSubIds(@NonNull NetworkCapabilities nc) { + private void combineSubscriptionIds(@NonNull NetworkCapabilities nc) { if (!Objects.equals(mSubIds, nc.mSubIds)) { throw new IllegalStateException("Can't combine two subscription ID sets"); } @@ -2720,8 +2720,8 @@ public final class NetworkCapabilities implements Parcelable { */ @NonNull @SystemApi - public Builder setSubIds(@NonNull final Set subIds) { - mCaps.setSubIds(subIds); + public Builder setSubscriptionIds(@NonNull final Set subIds) { + mCaps.setSubscriptionIds(subIds); return this; } diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 3a8a07a55a..e8bd640aad 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -508,8 +508,8 @@ public class NetworkRequest implements Parcelable { */ @NonNull @SystemApi - public Builder setSubIds(@NonNull Set subIds) { - mNetworkCapabilities.setSubIds(subIds); + public Builder setSubscriptionIds(@NonNull Set subIds) { + mNetworkCapabilities.setSubscriptionIds(subIds); return this; } } From 27bfb8d418a8d275cebcb48283b6201b69644075 Mon Sep 17 00:00:00 2001 From: markchien Date: Tue, 20 Apr 2021 09:50:30 +0800 Subject: [PATCH 190/232] Change Connectivity resources SDK version to 30 S tethering module fail to sideload in R platform because package manager fail to parse S version sdk in R platform. Bug: 182409819 Test: m Change-Id: I35c63e4bfe7657afe1e7364926ab139b042b403e Merged-In: I35c63e4bfe7657afe1e7364926ab139b042b403e --- service/ServiceConnectivityResources/Android.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp index fa4501ac7f..7de1c6ee29 100644 --- a/service/ServiceConnectivityResources/Android.bp +++ b/service/ServiceConnectivityResources/Android.bp @@ -21,7 +21,7 @@ package { android_app { name: "ServiceConnectivityResources", - sdk_version: "module_current", + sdk_version: "module_30", resource_dirs: [ "res", ], From 8bee2fd622115a1c557b9835d86d15aa6f34b64c Mon Sep 17 00:00:00 2001 From: lucaslin Date: Wed, 21 Apr 2021 10:43:15 +0800 Subject: [PATCH 191/232] Add the related extra information in the javadoc Bug: 185876442 Test: make docs Change-Id: Ib0abc43e2009dbf5ee7b6c2a076424834d3d53f2 --- framework/src/android/net/ConnectivityManager.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 0418450ccd..35a3a40c27 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -424,6 +424,9 @@ public class ConnectivityManager { * Action used to display a dialog that asks the user whether to connect to a network that is * not validated. This intent is used to start the dialog in settings via startActivity. * + * This action includes a {@link Network} typed extra which is called + * {@link ConnectivityManager#EXTRA_NETWORK} that represents the network which is unvalidated. + * * @hide */ @SystemApi(client = MODULE_LIBRARIES) @@ -433,6 +436,10 @@ public class ConnectivityManager { * Action used to display a dialog that asks the user whether to avoid a network that is no * longer validated. This intent is used to start the dialog in settings via startActivity. * + * This action includes a {@link Network} typed extra which is called + * {@link ConnectivityManager#EXTRA_NETWORK} that represents the network which is no longer + * validated. + * * @hide */ @SystemApi(client = MODULE_LIBRARIES) @@ -444,6 +451,10 @@ public class ConnectivityManager { * that has not validated. This intent is used to start the dialog in settings via * startActivity. * + * This action includes a {@link Network} typed extra which is called + * {@link ConnectivityManager#EXTRA_NETWORK} that represents the network which has partial + * connectivity. + * * @hide */ @SystemApi(client = MODULE_LIBRARIES) From aab5a92321b47bede66597fc03272a43c87ebb87 Mon Sep 17 00:00:00 2001 From: junyulai Date: Wed, 21 Apr 2021 18:47:37 +0800 Subject: [PATCH 192/232] Do not deduce VCN capability if Builder is derived from request If the caller constructed the builder from a request, it means the user might explicitly want the capabilities from the request. Thus, the NOT_VCN_MANAGED capabilities should not be touched later. Test: TH Fix: 185876442 Change-Id: I92037cc8547fb72de12d6b6402f060f6c98e1853 --- framework/src/android/net/NetworkRequest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 3a8a07a55a..07b6c7d5be 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -220,6 +220,10 @@ public class NetworkRequest implements Parcelable { public Builder(@NonNull final NetworkRequest request) { Objects.requireNonNull(request); mNetworkCapabilities = request.networkCapabilities; + // If the caller constructed the builder from a request, it means the user + // might explicitly want the capabilities from the request. Thus, the NOT_VCN_MANAGED + // capabilities should not be touched later. + mModifiedNotVcnManaged = true; } /** From 51b8cb4889ea42d541e39003ed26a9958be7ea2d Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 22 Apr 2021 16:43:23 +0900 Subject: [PATCH 193/232] Add AOSP certificates for connectivity resources Instead of the platform certificate, use a dedicated certificate. The AOSP certificates are only used for testing as they have known keys, and are replaced when resigning production images. Key generated with: openssl req -x509 -newkey rsa:4096 -nodes -days 999999 -keyout key.pem \ -out com.android.connectivity.resources.x509.pem openssl pkcs8 -topk8 -inform PEM -outform DER -in key.pem -out \ com.android.connectivity.resources.pk8 -nocrypt Fixes: 184808224 Fixes: 185462051 Test: m Change-Id: I25cddc8d5ab948da9d3a2dbcd202ece1f61dd5a2 --- .../ServiceConnectivityResources/Android.bp | 8 ++- .../com.android.connectivity.resources.pk8 | Bin 0 -> 2375 bytes ...om.android.connectivity.resources.x509.pem | 36 ++++++++++++ .../resources-certs/key.pem | 52 ++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 service/ServiceConnectivityResources/resources-certs/com.android.connectivity.resources.pk8 create mode 100644 service/ServiceConnectivityResources/resources-certs/com.android.connectivity.resources.x509.pem create mode 100644 service/ServiceConnectivityResources/resources-certs/key.pem diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp index fa4501ac7f..552a0778d3 100644 --- a/service/ServiceConnectivityResources/Android.bp +++ b/service/ServiceConnectivityResources/Android.bp @@ -30,6 +30,10 @@ android_app { apex_available: [ "com.android.tethering", ], - // TODO: use a dedicated cert once generated - certificate: "platform", + certificate: ":com.android.connectivity.resources.certificate", +} + +android_app_certificate { + name: "com.android.connectivity.resources.certificate", + certificate: "resources-certs/com.android.connectivity.resources", } diff --git a/service/ServiceConnectivityResources/resources-certs/com.android.connectivity.resources.pk8 b/service/ServiceConnectivityResources/resources-certs/com.android.connectivity.resources.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..bfdc28b313ec197438aa5fa9a0ab16c2a2d711f8 GIT binary patch literal 2375 zcmV-N3Apw!f(b(c0RS)!1_>&LNQUwEii%!DFOii0)heo0JrN?Q}(2@ zhkJ`~utV#y%@b4?-zhK~EIhlhG0v)NsIaqU!-5qOP$iFop12KY4D>a3jkg}dE}H!~ zC6;V$N=*;IHdK6rtU{_#B;yxi#=s{cP*fxzbom)%acbp=MmswPDkAOi{-?g>M0bj^ z1!UkUG;^l!tq+YEdsSd3VUWK*85Ytau$&u&_orPvf?W8kqNpbiDfwU*kK>uK(`W8( zy;XRsN9euI_~3cawF&|bh7s{H3(5BHEU%L`08;m zFqxyQpHZP(BLJgPj;x>PEy8cYPEY?u#L4Mm@9@%6k)vWDAXvfH2JzMv}OL))SlVN>;lXF4%ig+;A&yM@4SdxX_sXbiSiL_3Os zSk@?48E)pkS@YX$SdArjI2g!2PV0w;+I2{Z-kASgY3h(BO%4_)b_^nJ@lXw{i8IIG zzXk2YG4hl##IioCm6|^69%{f)N)S6B2RlzuA}tYOv?HuqQeT1oj%&IT!WRHq9?3f= z{uYiNj#`z$#PB-HI$-0*f$qzJ1*_zG4n?=BPUYa6FU#SdIru;UH0w3yYlmk0EOU`yjd5s7Z{b4-xL%Kc&`dH-Joo&Je1&Iv7tb37xO*m zekIldj>^3ul@2tW4kQJp+na0_IG~lzV#KsrmS5CW(%9FeOH&G{EE-z~%98TiU#_DM z5(bFjk)N?S5QW)zuNv->D%HY}XHW?=gCZnLReJl+gH8K!PL-EnxFB z1`H)iAI{=1$;&v1?@T!Hl=L~f0?pquY z@@~$prwf_UJBi%l6<#0caMe+PBZ5fCtH0v7yLi)~d?S4|)N|QfpWxn|lj#~|78gqy z^L(vlZC({v24<7`_l-iqq@Hig#c)VCdfk)VY@s!CbMZI2j?~~+^e@l%wLQZCL0H5! z)ChkT*oIl_HE))w&mV`S#>3b%s_i2Rx!SjmOtWzdy85Zxu750IjgQ-PYe{@4yK>A2i4*>^+63)Ff)0)hbn0L#bkKCBFY z_RVU^;`X7H8{uDwSAW1mC4i|_R;a#PF?zHhCtM^KUJbzPT1!H;{Do4TGdb$O_zdI) z&)`#_xDid0u3UwgkBcx!P&3KlGe;xATp9;4Y@x3W)km>PTK*nuCR^1QvQsHu3XRJX zj$Un*8wuma(teOlKzdRtP^`f|qmI|24UUnPeFhYy2q-b?r*b-GeMI9Y-H43+R&x)2 z4PS0BUPrq%%-rRz9|OPI3ijDEBayYiCf&|UAlU6{+S}k-wrflPH&YI*JwqS2OXTF7 zI%}YdlD8jgEBC}P@Vw9XKXKqc$o*Ll8n?@B9+$(RY^~k)ccdGm{Q`mk01c}V`?fk0 zmIyX~>5KVBw>JZK+O!hpW>n63%--?kCSmHY8R(TnN61w@A_EwEVq9MAEPO$D(PZw6O=l1BL7qN(2K4W^r(+xtE<@NqLpvdtQbMxM!xHUIs+Q> z5hFhdQ;p@XD4BX1$<23p)AZ#9BLX|XT&Bw4u3jW2JUZhTP`{#otfm%X+~L_6N+Xa} zsvG0#ioNs%pocHiOVMU^m$nFfPO6`llov6`xuZA$brdaeIeJ>{{i##8G6N`7c> zu1q|E`n<1q^*P@G;c)rICzu9oEy^#lcly-lf`I>muJ1Y-lYB1$4d_R+F0~lRvgsWP zX?;k6TY~yM4`?lTsepNn_CxEwecw;ov1)^ws!p24XWD;@^Uo8_83KX<0RV$8HW*(R zW;zpt%TdG3(6&J0(m%mEUSLL{x^*>$Vi=?NnFrb(*|@V2<{01`WXviA<&~ITq~M6m zP2v)Gx$u7G>6zdYv>PmVr*FuUYp>K5?)%}JGvvs5;bv$UbkWr9jmIf@kpqC)BLMEv zhtZYVC*|JgYJ)>36@0KgMfO&0o0|j|o%MaejgwvjrUc!dq)<)*vT%qFNPSdUbvW?PX Date: Sat, 17 Apr 2021 13:46:25 +0800 Subject: [PATCH 194/232] Rename getAllNetworkStateSnapshot which should be pluralized Address API review feedback, ConnectivityManager#getAllNetworkStateSnapshot should be pluralized so rename the method to getAllNetworkStateSnapshots (cherry picked from ag/14221105) Bug: 183972554 Test: make, FrameworksNetTests FrameworksServicesTests Merged-In: Ic18d17d05984fa2466c962c7843c0ef7183ce77c Change-Id: Ic18d17d05984fa2466c962c7843c0ef7183ce77c --- framework/api/module-lib-current.txt | 2 +- framework/src/android/net/ConnectivityManager.java | 4 ++-- framework/src/android/net/IConnectivityManager.aidl | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 9e2cd3e8a7..c8b04a3f63 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -7,7 +7,7 @@ package android.net { public class ConnectivityManager { method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset(); - method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshot(); + method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshots(); method @Nullable public android.net.ProxyInfo getGlobalProxy(); method @NonNull public static android.util.Range getIpSecNetIdRange(); method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 4dd0984c79..ae9b8e6a8e 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1435,9 +1435,9 @@ public class ConnectivityManager { android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) @NonNull - public List getAllNetworkStateSnapshot() { + public List getAllNetworkStateSnapshots() { try { - return mService.getAllNetworkStateSnapshot(); + return mService.getAllNetworkStateSnapshots(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index 0826922e21..a7cb618f97 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -81,7 +81,7 @@ interface IConnectivityManager @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) NetworkState[] getAllNetworkState(); - List getAllNetworkStateSnapshot(); + List getAllNetworkStateSnapshots(); boolean isActiveNetworkMetered(); From 962d13bd3e3fc56f4090b7d01904fe2b45188ed5 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 19 Mar 2021 17:41:48 +0900 Subject: [PATCH 195/232] Use connectivity resources in service-connectivity Migrate resource usage to the connectivity resource package. For framework resources that have known overlays, keep a fallback until the overlays can be migrated. Bug: 182125649 Test: atest FrameworksNetTests Merged-In: I778d94a5aac0c4e20e78b1ba3a002495c17a38a0 (clean cherry-pick) Change-Id: I778d94a5aac0c4e20e78b1ba3a002495c17a38a0 --- .../res/values/config.xml | 34 +++++++++++++++++++ .../res/values/overlayable.xml | 6 ++++ 2 files changed, 40 insertions(+) diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml index 71674e4dc6..9ff2a2209e 100644 --- a/service/ServiceConnectivityResources/res/values/config.xml +++ b/service/ServiceConnectivityResources/res/values/config.xml @@ -78,6 +78,11 @@ 1,3 + + 2 + + + 2 1 + + + 10 + 11 + 12 + 14 + 15 + + + + false + + + + 0 + + + 0 + + + 0 + + + + + diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml index 25e19cedbb..717d08e13e 100644 --- a/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -26,6 +26,12 @@ + + + + + + From 9075ae36be793ed0b0e9b03e131d76532a1b19d1 Mon Sep 17 00:00:00 2001 From: Chiachang Wang Date: Tue, 20 Apr 2021 15:41:24 +0800 Subject: [PATCH 196/232] Address API review feedback Address API review feedback to: - Rename NetworkAgent#setTeardownDelayMs to NetworkAgent#setTeardownDelayMillis - Use getters instead of fields in VpnTransportInfo - Rename registerDefaultNetworkCallbackAsUid to registerDefaultNetworkCallbackForUid in ConnectiivityManager Bug: 183972850 Bug: 185246410 Fix: 184735863 Test: make update-api Test: atest FrameworksNetTests Test: atest CtsNetTestCasesLatestSdk Change-Id: I5e8c4bed8bda40d507afa894c359b5e24ee5d868 Merged-In: I5e8c4bed8bda40d507afa894c359b5e24ee5d868 --- framework/api/module-lib-current.txt | 6 +-- framework/api/system-current.txt | 2 +- .../src/android/net/ConnectivityManager.java | 4 +- framework/src/android/net/NetworkAgent.java | 8 ++-- .../src/android/net/VpnTransportInfo.java | 37 +++++++++++++------ 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 9e2cd3e8a7..8b10fcc62b 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -11,7 +11,7 @@ package android.net { method @Nullable public android.net.ProxyInfo getGlobalProxy(); method @NonNull public static android.util.Range getIpSecNetIdRange(); method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress); @@ -166,11 +166,11 @@ package android.net { public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { ctor public VpnTransportInfo(int, @Nullable String); method public int describeContents(); + method @Nullable public String getSessionId(); + method public int getType(); method @NonNull public android.net.VpnTransportInfo makeCopy(long); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field @Nullable public final String sessionId; - field public final int type; } } diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index de673ee971..fc65fec07b 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -238,7 +238,7 @@ package android.net { method public final void sendQosSessionLost(int, int, int); method public final void sendSocketKeepaliveEvent(int, int); method @Deprecated public void setLegacySubtype(int, @NonNull String); - method public void setTeardownDelayMs(@IntRange(from=0, to=0x1388) int); + method public void setTeardownDelayMillis(@IntRange(from=0, to=0x1388) int); method public final void setUnderlyingNetworks(@Nullable java.util.List); method public void unregister(); field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 043ff383e7..c04239530b 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4407,7 +4407,7 @@ public class ConnectivityManager { @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, @NonNull Handler handler) { - registerDefaultNetworkCallbackAsUid(Process.INVALID_UID, networkCallback, handler); + registerDefaultNetworkCallbackForUid(Process.INVALID_UID, networkCallback, handler); } /** @@ -4437,7 +4437,7 @@ public class ConnectivityManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) - public void registerDefaultNetworkCallbackAsUid(int uid, + public void registerDefaultNetworkCallbackForUid(int uid, @NonNull NetworkCallback networkCallback, @NonNull Handler handler) { CallbackHandler cbHandler = new CallbackHandler(handler); sendRequestForNetwork(uid, null /* need */, networkCallback, 0 /* timeoutMs */, diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index c57da53f28..f65acdd5b4 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -879,11 +879,11 @@ public abstract class NetworkAgent { * This method may be called at any time while the network is connected. It has no effect if * the network is already disconnected and the teardown delay timer is running. * - * @param teardownDelayMs the teardown delay to set, or 0 to disable teardown delay. + * @param teardownDelayMillis the teardown delay to set, or 0 to disable teardown delay. */ - public void setTeardownDelayMs( - @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int teardownDelayMs) { - queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMs)); + public void setTeardownDelayMillis( + @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int teardownDelayMillis) { + queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMillis)); } /** diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java index efd3363771..4071c9ab71 100644 --- a/framework/src/android/net/VpnTransportInfo.java +++ b/framework/src/android/net/VpnTransportInfo.java @@ -40,10 +40,10 @@ import java.util.Objects; @SystemApi(client = MODULE_LIBRARIES) public final class VpnTransportInfo implements TransportInfo, Parcelable { /** Type of this VPN. */ - public final int type; + private final int mType; @Nullable - public final String sessionId; + private final String mSessionId; @Override public @RedactionType long getApplicableRedactions() { @@ -55,13 +55,28 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { */ @NonNull public VpnTransportInfo makeCopy(@RedactionType long redactions) { - return new VpnTransportInfo(type, - ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : sessionId); + return new VpnTransportInfo(mType, + ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : mSessionId); } public VpnTransportInfo(int type, @Nullable String sessionId) { - this.type = type; - this.sessionId = sessionId; + this.mType = type; + this.mSessionId = sessionId; + } + + /** + * Returns the session Id of this VpnTransportInfo. + */ + @Nullable + public String getSessionId() { + return mSessionId; + } + + /** + * Returns the type of this VPN. + */ + public int getType() { + return mType; } @Override @@ -69,17 +84,17 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { if (!(o instanceof VpnTransportInfo)) return false; VpnTransportInfo that = (VpnTransportInfo) o; - return (this.type == that.type) && TextUtils.equals(this.sessionId, that.sessionId); + return (this.mType == that.mType) && TextUtils.equals(this.mSessionId, that.mSessionId); } @Override public int hashCode() { - return Objects.hash(type, sessionId); + return Objects.hash(mType, mSessionId); } @Override public String toString() { - return String.format("VpnTransportInfo{type=%d, sessionId=%s}", type, sessionId); + return String.format("VpnTransportInfo{type=%d, sessionId=%s}", mType, mSessionId); } @Override @@ -89,8 +104,8 @@ public final class VpnTransportInfo implements TransportInfo, Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(type); - dest.writeString(sessionId); + dest.writeInt(mType); + dest.writeString(mSessionId); } public static final @NonNull Creator CREATOR = From 070bdd4b77333843ad97e7333976988720ea25ff Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Fri, 30 Apr 2021 20:22:10 +0900 Subject: [PATCH 197/232] Add doc to getNetworkCapabilities Test: doc-only change Fixes: 158092418 Change-Id: Ic20fb55e1bdd4e836468794d1f86d3e9d0bc5965 --- framework/src/android/net/ConnectivityManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 4636abb8ae..e0b9229ee0 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1561,7 +1561,7 @@ public class ConnectivityManager { /** * Get the {@link NetworkCapabilities} for the given {@link Network}. This - * will return {@code null} if the network is unknown. + * will return {@code null} if the network is unknown or if the |network| argument is null. * * This will remove any location sensitive data in {@link TransportInfo} embedded in * {@link NetworkCapabilities#getTransportInfo()}. Some transport info instances like From 57f9ba8b4055b688d2bf9be57dc888f28357fb6a Mon Sep 17 00:00:00 2001 From: lucaslin Date: Fri, 23 Apr 2021 21:03:39 +0800 Subject: [PATCH 198/232] Update PrivateDnsMode from StringDef to IntDef Update PrivateDnsMode from StringDef to IntDef because IntDef is the normal way of representing multiple choices in public API. Also update other related files. Bug: 185311744 Test: 1. make update-api 2. atest FrameworksNetTests CtsNetTestCases CtsNetTestCasesLatestSdk 3. atest FrameworksServicesTests:DevicePolicyManagerTest Change-Id: I23e7ec140066979726d769cabc5f7057bb2167e6 Merged-In: I23e7ec140066979726d769cabc5f7057bb2167e6 (Cherry-picked from ag/14227609) --- framework/api/module-lib-current.txt | 12 +-- .../src/android/net/ConnectivityManager.java | 77 -------------- .../net/ConnectivitySettingsManager.java | 100 ++++++++++++++++-- 3 files changed, 100 insertions(+), 89 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index a8e2517a5e..90580fa17b 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -10,7 +10,6 @@ package android.net { method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshots(); method @Nullable public android.net.ProxyInfo getGlobalProxy(); method @NonNull public static android.util.Range getIpSecNetIdRange(); - method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); @@ -20,7 +19,6 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean); - method public static void setPrivateDnsMode(@NonNull android.content.Context, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection>); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); @@ -40,9 +38,6 @@ package android.net { field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10 field public static final int BLOCKED_REASON_NONE = 0; // 0x0 field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8 - field public static final String PRIVATE_DNS_MODE_OFF = "off"; - field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; - field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0 field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 } @@ -69,6 +64,7 @@ package android.net { method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context); method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context); + method public static int getPrivateDnsMode(@NonNull android.content.Context); method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean); method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String); @@ -85,8 +81,9 @@ package android.net { method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String); method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int); method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); - method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull String); + method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull int); method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String); + method public static void setPrivateDnsMode(@NonNull android.content.Context, int); method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean); method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2 @@ -95,6 +92,9 @@ package android.net { field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2 field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0 field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1 + field public static final int PRIVATE_DNS_MODE_OFF = 1; // 0x1 + field public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; // 0x2 + field public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; // 0x3 } public final class NetworkAgentConfig implements android.os.Parcelable { diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 19764dd896..0a3e231237 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -16,8 +16,6 @@ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; -import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE; -import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE; import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST; @@ -33,7 +31,6 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.StringDef; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -41,7 +38,6 @@ import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.ConnectivityDiagnosticsManager.DataStallReport.DetectionMethod; @@ -70,7 +66,6 @@ import android.os.UserHandle; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.util.Range; @@ -820,38 +815,6 @@ public class ConnectivityManager { */ public static final int NETID_UNSET = 0; - /** - * Private DNS Mode values. - * - * The "private_dns_mode" global setting stores a String value which is - * expected to be one of the following. - */ - - /** - * @hide - */ - @SystemApi(client = MODULE_LIBRARIES) - public static final String PRIVATE_DNS_MODE_OFF = "off"; - /** - * @hide - */ - @SystemApi(client = MODULE_LIBRARIES) - public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"; - /** - * @hide - */ - @SystemApi(client = MODULE_LIBRARIES) - public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname"; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @StringDef(value = { - PRIVATE_DNS_MODE_OFF, - PRIVATE_DNS_MODE_OPPORTUNISTIC, - PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, - }) - public @interface PrivateDnsMode {} - /** * Flag to indicate that an app is not subject to any restrictions that could result in its * network access blocked. @@ -5448,44 +5411,4 @@ public class ConnectivityManager { public static Range getIpSecNetIdRange() { return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1); } - - /** - * Get private DNS mode from settings. - * - * @param context The Context to query the private DNS mode from settings. - * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants. - * - * @hide - */ - @SystemApi(client = MODULE_LIBRARIES) - @NonNull - @PrivateDnsMode - public static String getPrivateDnsMode(@NonNull Context context) { - final ContentResolver cr = context.getContentResolver(); - String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE); - if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE); - // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose - // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode. - if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC; - return mode; - } - - /** - * Set private DNS mode to settings. - * - * @param context The {@link Context} to set the private DNS mode. - * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants. - * - * @hide - */ - @SystemApi(client = MODULE_LIBRARIES) - public static void setPrivateDnsMode(@NonNull Context context, - @NonNull @PrivateDnsMode String mode) { - if (!(mode == PRIVATE_DNS_MODE_OFF - || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC - || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { - throw new IllegalArgumentException("Invalid private dns mode"); - } - Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, mode); - } } diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index 9a00055e00..31e1fb0581 100644 --- a/framework/src/android/net/ConnectivitySettingsManager.java +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -19,18 +19,15 @@ package android.net; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE; import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY; -import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF; -import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; -import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.ContentResolver; import android.content.Context; import android.net.ConnectivityManager.MultipathPreference; -import android.net.ConnectivityManager.PrivateDnsMode; import android.provider.Settings; import android.text.TextUtils; import android.util.Range; @@ -340,6 +337,37 @@ public class ConnectivitySettingsManager { */ public static final String MOBILE_DATA_PREFERRED_APPS = "mobile_data_preferred_apps"; + /** + * One of the private DNS modes that indicates the private DNS mode is off. + */ + public static final int PRIVATE_DNS_MODE_OFF = 1; + + /** + * One of the private DNS modes that indicates the private DNS mode is automatic, which + * will try to use the current DNS as private DNS. + */ + public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; + + /** + * One of the private DNS modes that indicates the private DNS mode is strict and the + * {@link #PRIVATE_DNS_SPECIFIER} is required, which will try to use the value of + * {@link #PRIVATE_DNS_SPECIFIER} as private DNS. + */ + public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + PRIVATE_DNS_MODE_OFF, + PRIVATE_DNS_MODE_OPPORTUNISTIC, + PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, + }) + public @interface PrivateDnsMode {} + + private static final String PRIVATE_DNS_MODE_OFF_STRING = "off"; + private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING = "opportunistic"; + private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname"; + /** * Get mobile data activity timeout from {@link Settings}. * @@ -689,6 +717,65 @@ public class ConnectivitySettingsManager { context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */); } + private static String getPrivateDnsModeAsString(@PrivateDnsMode int mode) { + switch (mode) { + case PRIVATE_DNS_MODE_OFF: + return PRIVATE_DNS_MODE_OFF_STRING; + case PRIVATE_DNS_MODE_OPPORTUNISTIC: + return PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING; + case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: + return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING; + default: + throw new IllegalArgumentException("Invalid private dns mode: " + mode); + } + } + + private static int getPrivateDnsModeAsInt(String mode) { + switch (mode) { + case "off": + return PRIVATE_DNS_MODE_OFF; + case "hostname": + return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; + case "opportunistic": + return PRIVATE_DNS_MODE_OPPORTUNISTIC; + default: + throw new IllegalArgumentException("Invalid private dns mode: " + mode); + } + } + + /** + * Get private DNS mode from settings. + * + * @param context The Context to query the private DNS mode from settings. + * @return A string of private DNS mode. + */ + @PrivateDnsMode + public static int getPrivateDnsMode(@NonNull Context context) { + final ContentResolver cr = context.getContentResolver(); + String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE); + if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE); + // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose + // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode. + if (TextUtils.isEmpty(mode)) return PRIVATE_DNS_MODE_OPPORTUNISTIC; + return getPrivateDnsModeAsInt(mode); + } + + /** + * Set private DNS mode to settings. + * + * @param context The {@link Context} to set the private DNS mode. + * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants. + */ + public static void setPrivateDnsMode(@NonNull Context context, @PrivateDnsMode int mode) { + if (!(mode == PRIVATE_DNS_MODE_OFF + || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC + || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { + throw new IllegalArgumentException("Invalid private dns mode: " + mode); + } + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, + getPrivateDnsModeAsString(mode)); + } + /** * Get specific private dns provider name from {@link Settings}. * @@ -731,13 +818,14 @@ public class ConnectivitySettingsManager { * constants. */ public static void setPrivateDnsDefaultMode(@NonNull Context context, - @NonNull @PrivateDnsMode String mode) { + @NonNull @PrivateDnsMode int mode) { if (!(mode == PRIVATE_DNS_MODE_OFF || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) { throw new IllegalArgumentException("Invalid private dns mode"); } - Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE, mode); + Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE, + getPrivateDnsModeAsString(mode)); } /** From 778c86525b9b8e4b3ca270241490de9736051601 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Tue, 11 May 2021 15:42:10 +0900 Subject: [PATCH 199/232] Allow any transport with TEST if the network is restricted This will allow CTS to add the WIFI transport and others, letting them test a number of essential features of the ranking algorithm. It's relatively safe because restricted networks can never become the default, and NOT_RESTRICTED is a default capability so very few apps would be affected by the shell creating such a network. Bug: 184037351 Test: NetworkScoreTest (which is under review) Change-Id: I21055dc613fead6130adc2122f2cdd0af9b49adf --- .../src/android/net/NetworkCapabilities.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 937a9d2393..8e26ceb1ff 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -830,8 +830,17 @@ public final class NetworkCapabilities implements Parcelable { final int[] originalAdministratorUids = getAdministratorUids(); final TransportInfo originalTransportInfo = getTransportInfo(); clearAll(); - mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS) - | (1 << TRANSPORT_TEST); + if (0 != (originalCapabilities & NET_CAPABILITY_NOT_RESTRICTED)) { + // If the test network is not restricted, then it is only allowed to declare some + // specific transports. This is to minimize impact on running apps in case an app + // run from the shell creates a test a network. + mTransportTypes = + (originalTransportTypes & UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS) + | (1 << TRANSPORT_TEST); + } else { + // If the test transport is restricted, then it may declare any transport. + mTransportTypes = (originalTransportTypes | (1 << TRANSPORT_TEST)); + } mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES; mNetworkSpecifier = originalSpecifier; mSignalStrength = originalSignalStrength; @@ -935,9 +944,10 @@ public final class NetworkCapabilities implements Parcelable { }; /** - * Allowed transports on a test network, in addition to TRANSPORT_TEST. + * Allowed transports on an unrestricted test network (in addition to TRANSPORT_TEST). */ - private static final int TEST_NETWORKS_ALLOWED_TRANSPORTS = 1 << TRANSPORT_TEST + private static final int UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS = + 1 << TRANSPORT_TEST // Test ethernet networks can be created with EthernetManager#setIncludeTestInterfaces | 1 << TRANSPORT_ETHERNET // Test VPN networks can be created but their UID ranges must be empty. From 395fafb54fa873af780d1d29f69d5ec1f906cf9f Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Tue, 27 Apr 2021 10:37:09 +0900 Subject: [PATCH 200/232] Rename unwanted capabilities to forbidden capabilities. Addresses API council feedback. Bug: 184890428 Test: atest FrameworksNetTests CtsNetTestCases Test: atest CtsNetTestCasesLatestSdk:NetworkCapabilitiesTest on R device Change-Id: Id7c68fbf56ee08fcad8e8e3aacf037fa1885936b --- framework/api/module-lib-current.txt | 10 +- .../src/android/net/NetworkCapabilities.java | 92 +++++++++---------- framework/src/android/net/NetworkRequest.java | 31 ++++--- 3 files changed, 67 insertions(+), 66 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index a8e2517a5e..a60f87cbe8 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -109,7 +109,7 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { method @Nullable public java.util.Set> getUids(); - method public boolean hasUnwantedCapability(int); + method public boolean hasForbiddenCapability(int); field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L @@ -123,13 +123,13 @@ package android.net { } public class NetworkRequest implements android.os.Parcelable { - method @NonNull public int[] getUnwantedCapabilities(); - method public boolean hasUnwantedCapability(int); + method @NonNull public int[] getForbiddenCapabilities(); + method public boolean hasForbiddenCapability(int); } public static class NetworkRequest.Builder { - method @NonNull public android.net.NetworkRequest.Builder addUnwantedCapability(int); - method @NonNull public android.net.NetworkRequest.Builder removeUnwantedCapability(int); + method @NonNull public android.net.NetworkRequest.Builder addForbiddenCapability(int); + method @NonNull public android.net.NetworkRequest.Builder removeForbiddenCapability(int); method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set>); } diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 937a9d2393..5d3eaaf106 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -183,7 +183,7 @@ public final class NetworkCapabilities implements Parcelable { throw new UnsupportedOperationException( "Cannot clear NetworkCapabilities when mRedactions is set"); } - mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0; + mNetworkCapabilities = mTransportTypes = mForbiddenNetworkCapabilities = 0; mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; mNetworkSpecifier = null; mTransportInfo = null; @@ -219,7 +219,7 @@ public final class NetworkCapabilities implements Parcelable { mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids); setAdministratorUids(nc.getAdministratorUids()); mOwnerUid = nc.mOwnerUid; - mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; + mForbiddenNetworkCapabilities = nc.mForbiddenNetworkCapabilities; mSSID = nc.mSSID; mPrivateDnsBroken = nc.mPrivateDnsBroken; mRequestorUid = nc.mRequestorUid; @@ -237,7 +237,7 @@ public final class NetworkCapabilities implements Parcelable { /** * If any capabilities specified here they must not exist in the matching Network. */ - private long mUnwantedNetworkCapabilities; + private long mForbiddenNetworkCapabilities; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -586,21 +586,21 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public @NonNull NetworkCapabilities addCapability(@NetCapability int capability) { - // If the given capability was previously added to the list of unwanted capabilities - // then the capability will also be removed from the list of unwanted capabilities. - // TODO: Consider adding unwanted capabilities to the public API and mention this + // If the given capability was previously added to the list of forbidden capabilities + // then the capability will also be removed from the list of forbidden capabilities. + // TODO: Consider adding forbidden capabilities to the public API and mention this // in the documentation. checkValidCapability(capability); mNetworkCapabilities |= 1L << capability; - // remove from unwanted capability list - mUnwantedNetworkCapabilities &= ~(1L << capability); + // remove from forbidden capability list + mForbiddenNetworkCapabilities &= ~(1L << capability); return this; } /** - * Adds the given capability to the list of unwanted capabilities of this + * Adds the given capability to the list of forbidden capabilities of this * {@code NetworkCapability} instance. Note that when searching for a network to - * satisfy a request, the network must not contain any capability from unwanted capability + * satisfy a request, the network must not contain any capability from forbidden capability * list. *

      * If the capability was previously added to the list of required capabilities (for @@ -610,9 +610,9 @@ public final class NetworkCapabilities implements Parcelable { * @see #addCapability(int) * @hide */ - public void addUnwantedCapability(@NetCapability int capability) { + public void addForbiddenCapability(@NetCapability int capability) { checkValidCapability(capability); - mUnwantedNetworkCapabilities |= 1L << capability; + mForbiddenNetworkCapabilities |= 1L << capability; mNetworkCapabilities &= ~(1L << capability); // remove from requested capabilities } @@ -632,16 +632,16 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Removes (if found) the given unwanted capability from this {@code NetworkCapability} - * instance that were added via addUnwantedCapability(int) or setCapabilities(int[], int[]). + * Removes (if found) the given forbidden capability from this {@code NetworkCapability} + * instance that were added via addForbiddenCapability(int) or setCapabilities(int[], int[]). * * @param capability the capability to be removed. * @return This NetworkCapabilities instance, to facilitate chaining. * @hide */ - public @NonNull NetworkCapabilities removeUnwantedCapability(@NetCapability int capability) { + public @NonNull NetworkCapabilities removeForbiddenCapability(@NetCapability int capability) { checkValidCapability(capability); - mUnwantedNetworkCapabilities &= ~(1L << capability); + mForbiddenNetworkCapabilities &= ~(1L << capability); return this; } @@ -670,13 +670,13 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Gets all the unwanted capabilities set on this {@code NetworkCapability} instance. + * Gets all the forbidden capabilities set on this {@code NetworkCapability} instance. * - * @return an array of unwanted capability values for this instance. + * @return an array of forbidden capability values for this instance. * @hide */ - public @NetCapability int[] getUnwantedCapabilities() { - return NetworkCapabilitiesUtils.unpackBits(mUnwantedNetworkCapabilities); + public @NetCapability int[] getForbiddenCapabilities() { + return NetworkCapabilitiesUtils.unpackBits(mForbiddenNetworkCapabilities); } @@ -687,9 +687,9 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public void setCapabilities(@NetCapability int[] capabilities, - @NetCapability int[] unwantedCapabilities) { + @NetCapability int[] forbiddenCapabilities) { mNetworkCapabilities = NetworkCapabilitiesUtils.packBits(capabilities); - mUnwantedNetworkCapabilities = NetworkCapabilitiesUtils.packBits(unwantedCapabilities); + mForbiddenNetworkCapabilities = NetworkCapabilitiesUtils.packBits(forbiddenCapabilities); } /** @@ -714,9 +714,9 @@ public final class NetworkCapabilities implements Parcelable { /** @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public boolean hasUnwantedCapability(@NetCapability int capability) { + public boolean hasForbiddenCapability(@NetCapability int capability) { return isValidCapability(capability) - && ((mUnwantedNetworkCapabilities & (1L << capability)) != 0); + && ((mForbiddenNetworkCapabilities & (1L << capability)) != 0); } /** @@ -746,14 +746,14 @@ public final class NetworkCapabilities implements Parcelable { private void combineNetCapabilities(@NonNull NetworkCapabilities nc) { final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities; - final long unwantedCaps = - this.mUnwantedNetworkCapabilities | nc.mUnwantedNetworkCapabilities; - if ((wantedCaps & unwantedCaps) != 0) { + final long forbiddenCaps = + this.mForbiddenNetworkCapabilities | nc.mForbiddenNetworkCapabilities; + if ((wantedCaps & forbiddenCaps) != 0) { throw new IllegalArgumentException( - "Cannot have the same capability in wanted and unwanted lists."); + "Cannot have the same capability in wanted and forbidden lists."); } this.mNetworkCapabilities = wantedCaps; - this.mUnwantedNetworkCapabilities = unwantedCaps; + this.mForbiddenNetworkCapabilities = forbiddenCaps; } /** @@ -764,7 +764,7 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public @Nullable String describeFirstNonRequestableCapability() { - final long nonRequestable = (mNetworkCapabilities | mUnwantedNetworkCapabilities) + final long nonRequestable = (mNetworkCapabilities | mForbiddenNetworkCapabilities) & NON_REQUESTABLE_CAPABILITIES; if (nonRequestable != 0) { @@ -781,28 +781,28 @@ public final class NetworkCapabilities implements Parcelable { private boolean satisfiedByNetCapabilities(@NonNull NetworkCapabilities nc, boolean onlyImmutable) { long requestedCapabilities = mNetworkCapabilities; - long requestedUnwantedCapabilities = mUnwantedNetworkCapabilities; + long requestedForbiddenCapabilities = mForbiddenNetworkCapabilities; long providedCapabilities = nc.mNetworkCapabilities; if (onlyImmutable) { requestedCapabilities &= ~MUTABLE_CAPABILITIES; - requestedUnwantedCapabilities &= ~MUTABLE_CAPABILITIES; + requestedForbiddenCapabilities &= ~MUTABLE_CAPABILITIES; } return ((providedCapabilities & requestedCapabilities) == requestedCapabilities) - && ((requestedUnwantedCapabilities & providedCapabilities) == 0); + && ((requestedForbiddenCapabilities & providedCapabilities) == 0); } /** @hide */ public boolean equalsNetCapabilities(@NonNull NetworkCapabilities nc) { return (nc.mNetworkCapabilities == this.mNetworkCapabilities) - && (nc.mUnwantedNetworkCapabilities == this.mUnwantedNetworkCapabilities); + && (nc.mForbiddenNetworkCapabilities == this.mForbiddenNetworkCapabilities); } private boolean equalsNetCapabilitiesRequestable(@NonNull NetworkCapabilities that) { - return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) == - (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)) - && ((this.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) == - (that.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)); + return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) + == (that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)) + && ((this.mForbiddenNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) + == (that.mForbiddenNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES)); } /** @@ -1718,7 +1718,7 @@ public final class NetworkCapabilities implements Parcelable { * Combine a set of Capabilities to this one. Useful for coming up with the complete set. *

      * Note that this method may break an invariant of having a particular capability in either - * wanted or unwanted lists but never in both. Requests that have the same capability in + * wanted or forbidden lists but never in both. Requests that have the same capability in * both lists will never be satisfied. * @hide */ @@ -1859,8 +1859,8 @@ public final class NetworkCapabilities implements Parcelable { public int hashCode() { return (int) (mNetworkCapabilities & 0xFFFFFFFF) + ((int) (mNetworkCapabilities >> 32) * 3) - + ((int) (mUnwantedNetworkCapabilities & 0xFFFFFFFF) * 5) - + ((int) (mUnwantedNetworkCapabilities >> 32) * 7) + + ((int) (mForbiddenNetworkCapabilities & 0xFFFFFFFF) * 5) + + ((int) (mForbiddenNetworkCapabilities >> 32) * 7) + ((int) (mTransportTypes & 0xFFFFFFFF) * 11) + ((int) (mTransportTypes >> 32) * 13) + mLinkUpBandwidthKbps * 17 @@ -1895,7 +1895,7 @@ public final class NetworkCapabilities implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mNetworkCapabilities); - dest.writeLong(mUnwantedNetworkCapabilities); + dest.writeLong(mForbiddenNetworkCapabilities); dest.writeLong(mTransportTypes); dest.writeInt(mLinkUpBandwidthKbps); dest.writeInt(mLinkDownBandwidthKbps); @@ -1919,7 +1919,7 @@ public final class NetworkCapabilities implements Parcelable { NetworkCapabilities netCap = new NetworkCapabilities(); netCap.mNetworkCapabilities = in.readLong(); - netCap.mUnwantedNetworkCapabilities = in.readLong(); + netCap.mForbiddenNetworkCapabilities = in.readLong(); netCap.mTransportTypes = in.readLong(); netCap.mLinkUpBandwidthKbps = in.readInt(); netCap.mLinkDownBandwidthKbps = in.readInt(); @@ -1973,9 +1973,9 @@ public final class NetworkCapabilities implements Parcelable { appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities, NetworkCapabilities::capabilityNameOf, "&"); } - if (0 != mUnwantedNetworkCapabilities) { - sb.append(" Unwanted: "); - appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities, + if (0 != mForbiddenNetworkCapabilities) { + sb.append(" Forbidden: "); + appendStringRepresentationOfBitMaskToStringBuilder(sb, mForbiddenNetworkCapabilities, NetworkCapabilities::capabilityNameOf, "&"); } if (mLinkUpBandwidthKbps > 0) { diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 8c4f4193b5..dd88c5a5c9 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -311,7 +311,7 @@ public class NetworkRequest implements Parcelable { * * @see #addCapability(int) * - * @param capability The capability to add to unwanted capability list. + * @param capability The capability to add to forbidden capability list. * @return The builder to facilitate chaining. * * @hide @@ -319,15 +319,15 @@ public class NetworkRequest implements Parcelable { @NonNull @SuppressLint("MissingGetterMatchingBuilder") @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public Builder addUnwantedCapability(@NetworkCapabilities.NetCapability int capability) { - mNetworkCapabilities.addUnwantedCapability(capability); + public Builder addForbiddenCapability(@NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.addForbiddenCapability(capability); return this; } /** - * Removes (if found) the given unwanted capability from this builder instance. + * Removes (if found) the given forbidden capability from this builder instance. * - * @param capability The unwanted capability to remove. + * @param capability The forbidden capability to remove. * @return The builder to facilitate chaining. * * @hide @@ -335,8 +335,9 @@ public class NetworkRequest implements Parcelable { @NonNull @SuppressLint("BuilderSetStyle") @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public Builder removeUnwantedCapability(@NetworkCapabilities.NetCapability int capability) { - mNetworkCapabilities.removeUnwantedCapability(capability); + public Builder removeForbiddenCapability( + @NetworkCapabilities.NetCapability int capability) { + mNetworkCapabilities.removeForbiddenCapability(capability); return this; } @@ -598,13 +599,13 @@ public class NetworkRequest implements Parcelable { } /** - * @see Builder#addUnwantedCapability(int) + * @see Builder#addForbiddenCapability(int) * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public boolean hasUnwantedCapability(@NetCapability int capability) { - return networkCapabilities.hasUnwantedCapability(capability); + public boolean hasForbiddenCapability(@NetCapability int capability) { + return networkCapabilities.hasForbiddenCapability(capability); } /** @@ -709,18 +710,18 @@ public class NetworkRequest implements Parcelable { } /** - * Gets all the unwanted capabilities set on this {@code NetworkRequest} instance. + * Gets all the forbidden capabilities set on this {@code NetworkRequest} instance. * - * @return an array of unwanted capability values for this instance. + * @return an array of forbidden capability values for this instance. * * @hide */ @NonNull @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public @NetCapability int[] getUnwantedCapabilities() { - // No need to make a defensive copy here as NC#getUnwantedCapabilities() already returns + public @NetCapability int[] getForbiddenCapabilities() { + // No need to make a defensive copy here as NC#getForbiddenCapabilities() already returns // a new array. - return networkCapabilities.getUnwantedCapabilities(); + return networkCapabilities.getForbiddenCapabilities(); } /** From e4d51c971051505f6e53541d7bf5a31788c66c33 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 19 Mar 2021 00:24:45 +0000 Subject: [PATCH 201/232] Build framework-connectivity using module_current framework-connectivity needs to build only against stable APIs. Bug: 171540887 Test: m framework-connectivity.impl Merged-In: I2d51d37d067bf6fe86e4dedf05855a2dd67ed57c Change-Id: I2d51d37d067bf6fe86e4dedf05855a2dd67ed57c --- framework/Android.bp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/framework/Android.bp b/framework/Android.bp index 017ff51f36..5b33328833 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -25,6 +25,7 @@ package { java_library { name: "framework-connectivity-protos", + sdk_version: "module_current", proto: { type: "nano", }, @@ -82,8 +83,7 @@ java_sdk_library { name: "framework-connectivity", api_only: true, defaults: ["framework-module-defaults"], - // TODO: build against module API - platform_apis: true, + installable: true, srcs: [ ":framework-connectivity-sources", ], @@ -100,18 +100,15 @@ java_sdk_library { libs: [ "unsupportedappusage", ], - permitted_packages: ["android.net", "com.android.connectivity.aidl"], + permitted_packages: [ + "android.net", + "com.android.connectivity.aidl", + ], } java_library { name: "framework-connectivity.impl", - // Instead of building against private API (framework.jar), - // build against core_platform + framework-minus-apex + module - // stub libs. This allows framework.jar to depend on this library, - // so it can be part of the private API until all clients have been migrated. - // TODO: just build against module_api, and remove this jar from - // the private API. - sdk_version: "core_platform", + sdk_version: "module_current", srcs: [ ":framework-connectivity-sources", ], @@ -122,10 +119,10 @@ java_library { ], }, libs: [ - "framework-minus-apex", - // TODO: just framework-tethering, framework-wifi when building against module_api - "framework-tethering.stubs.module_lib", - "framework-wifi.stubs.module_lib", + // TODO (b/183097033) remove once module_current includes core_current + "stable.core.platform.api.stubs", + "framework-tethering", + "framework-wifi", "unsupportedappusage", "ServiceConnectivityResources", ], @@ -136,5 +133,8 @@ java_library { jarjar_rules: "jarjar-rules.txt", apex_available: ["com.android.tethering"], installable: true, - permitted_packages: ["android.net", "com.android.connectivity.aidl"], + permitted_packages: [ + "android.net", + "com.android.connectivity.aidl", + ], } From 1fd558efeed93cdfb137925318bf8befd961cf6a Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 19 Mar 2021 10:13:40 +0000 Subject: [PATCH 202/232] Move NetworkUtils JNI out of core/jni Keep the utilities included via a library, but move them out of core/jni, and prepare a library to package them together with framework-connectivity. Also remove unnecessary dependencies in framework-connectivity. Bug: 171540887 Test: device boots and has connectivity Merged-In: I0b55dfe92f3cb6e848d79ac7953756f39aaa2597 Change-Id: I0b55dfe92f3cb6e848d79ac7953756f39aaa2597 --- framework/Android.bp | 53 +++- framework/jni/android_net_NetworkUtils.cpp | 302 +++++++++++++++++++++ framework/jni/onload.cpp | 38 +++ 3 files changed, 385 insertions(+), 8 deletions(-) create mode 100644 framework/jni/android_net_NetworkUtils.cpp create mode 100644 framework/jni/onload.cpp diff --git a/framework/Android.bp b/framework/Android.bp index 5b33328833..657d5a3d2e 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -100,9 +100,50 @@ java_sdk_library { libs: [ "unsupportedappusage", ], - permitted_packages: [ - "android.net", - "com.android.connectivity.aidl", + permitted_packages: ["android.net"], +} + +cc_defaults { + name: "libframework-connectivity-defaults", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + shared_libs: [ + "libbase", + "liblog", + "libnativehelper", + "libnetd_client", + ], + header_libs: [ + "dnsproxyd_protocol_headers", + ], +} + +cc_library_static { + name: "libconnectivityframeworkutils", + defaults: ["libframework-connectivity-defaults"], + srcs: [ + "jni/android_net_NetworkUtils.cpp", + ], + apex_available: [ + "//apex_available:platform", + "com.android.tethering", + ], +} + +cc_library_shared { + name: "libframework-connectivity-jni", + defaults: ["libframework-connectivity-defaults"], + srcs: [ + "jni/onload.cpp", + ], + static_libs: ["libconnectivityframeworkutils"], + apex_available: [ + "//apex_available:platform", + "com.android.tethering", ], } @@ -124,7 +165,6 @@ java_library { "framework-tethering", "framework-wifi", "unsupportedappusage", - "ServiceConnectivityResources", ], static_libs: [ "framework-connectivity-protos", @@ -133,8 +173,5 @@ java_library { jarjar_rules: "jarjar-rules.txt", apex_available: ["com.android.tethering"], installable: true, - permitted_packages: [ - "android.net", - "com.android.connectivity.aidl", - ], + permitted_packages: ["android.net"], } diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp new file mode 100644 index 0000000000..c70309f6ce --- /dev/null +++ b/framework/jni/android_net_NetworkUtils.cpp @@ -0,0 +1,302 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "NetworkUtils" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // NETID_USE_LOCAL_NAMESERVERS +#include +#include +#include +#include +#include + +#include "NetdClient.h" +#include "jni.h" + +extern "C" { +int ifc_enable(const char *ifname); +int ifc_disable(const char *ifname); +} + +#define NETUTILS_PKG_NAME "android/net/NetworkUtils" + +namespace android { + +constexpr int MAXPACKETSIZE = 8 * 1024; +// FrameworkListener limits the size of commands to 4096 bytes. +constexpr int MAXCMDSIZE = 4096; + +static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) { + jclass clazz = env->FindClass(class_name); + LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name); + return clazz; +} + +static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, + const char* method_signature) { + jmethodID res = env->GetMethodID(clazz, method_name, method_signature); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name, + method_signature); + return res; +} + +template +static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { + jobject res = env->NewGlobalRef(in); + LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference."); + return static_cast(res); +} + +static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { + ScopedLocalRef detailMessage(env, env->NewStringUTF(functionName)); + if (detailMessage.get() == NULL) { + // Not really much we can do here. We're probably dead in the water, + // but let's try to stumble on... + env->ExceptionClear(); + } + static jclass errnoExceptionClass = + MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException")); + + static jmethodID errnoExceptionCtor = + GetMethodIDOrDie(env, errnoExceptionClass, + "", "(Ljava/lang/String;I)V"); + + jobject exception = env->NewObject(errnoExceptionClass, + errnoExceptionCtor, + detailMessage.get(), + error); + env->Throw(reinterpret_cast(exception)); +} + +static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) +{ + struct sock_filter filter_code[] = { + // Reject all. + BPF_STMT(BPF_RET | BPF_K, 0) + }; + struct sock_fprog filter = { + sizeof(filter_code) / sizeof(filter_code[0]), + filter_code, + }; + + int fd = AFileDescriptor_getFD(env, javaFd); + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); + } +} + +static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) +{ + int optval_ignored = 0; + int fd = AFileDescriptor_getFD(env, javaFd); + if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) != + 0) { + jniThrowExceptionFmt(env, "java/net/SocketException", + "setsockopt(SO_DETACH_FILTER): %s", strerror(errno)); + } +} + +static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId) +{ + return (jboolean) !setNetworkForProcess(netId); +} + +static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz) +{ + return getNetworkForProcess(); +} + +static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz, + jint netId) +{ + return (jboolean) !setNetworkForResolv(netId); +} + +static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd, + jint netId) { + return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd)); +} + +static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) +{ + if (env->GetArrayLength(addr) != len) { + return false; + } + env->GetByteArrayRegion(addr, 0, len, reinterpret_cast(dst)); + return true; +} + +static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId, + jstring dname, jint ns_class, jint ns_type, jint flags) { + const jsize javaCharsCount = env->GetStringLength(dname); + const jsize byteCountUTF8 = env->GetStringUTFLength(dname); + + // Only allow dname which could be simply formatted to UTF8. + // In native layer, res_mkquery would re-format the input char array to packet. + std::vector queryname(byteCountUTF8 + 1, 0); + + env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data()); + int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkQuery", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId, + jbyteArray msg, jint msgLen, jint flags) { + uint8_t data[MAXCMDSIZE]; + + checkLenAndCopy(env, msg, msgLen, data); + int fd = resNetworkSend(netId, data, msgLen, flags); + + if (fd < 0) { + throwErrnoException(env, "resNetworkSend", -fd); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) { + int fd = AFileDescriptor_getFD(env, javaFd); + int rcode; + std::vector buf(MAXPACKETSIZE, 0); + + int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); + jniSetFileDescriptorOfFD(env, javaFd, -1); + if (res < 0) { + throwErrnoException(env, "resNetworkResult", -res); + return nullptr; + } + + jbyteArray answer = env->NewByteArray(res); + if (answer == nullptr) { + throwErrnoException(env, "resNetworkResult", ENOMEM); + return nullptr; + } else { + env->SetByteArrayRegion(answer, 0, res, + reinterpret_cast(buf.data())); + } + + jclass class_DnsResponse = env->FindClass("android/net/DnsResolver$DnsResponse"); + jmethodID ctor = env->GetMethodID(class_DnsResponse, "", "([BI)V"); + + return env->NewObject(class_DnsResponse, ctor, answer, rcode); +} + +static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) { + int fd = AFileDescriptor_getFD(env, javaFd); + resNetworkCancel(fd); + jniSetFileDescriptorOfFD(env, javaFd, -1); +} + +static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) { + unsigned dnsNetId = 0; + if (int res = getNetworkForDns(&dnsNetId) < 0) { + throwErrnoException(env, "getDnsNetId", -res); + return nullptr; + } + bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS; + + static jclass class_Network = MakeGlobalRefOrDie( + env, FindClassOrDie(env, "android/net/Network")); + static jmethodID ctor = env->GetMethodID(class_Network, "", "(IZ)V"); + return env->NewObject( + class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass); +} + +static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { + if (javaFd == NULL) { + jniThrowNullPointerException(env, NULL); + return NULL; + } + + int fd = AFileDescriptor_getFD(env, javaFd); + struct tcp_repair_window trw = {}; + socklen_t size = sizeof(trw); + + // Obtain the parameters of the TCP repair window. + int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size); + if (rc == -1) { + throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno); + return NULL; + } + + struct tcp_info tcpinfo = {}; + socklen_t tcpinfo_size = sizeof(tcp_info); + + // Obtain the window scale from the tcp info structure. This contains a scale factor that + // should be applied to the window size. + rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size); + if (rc == -1) { + throwErrnoException(env, "getsockopt : TCP_INFO", errno); + return NULL; + } + + jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow"); + jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "", "(IIIIII)V"); + + return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window, + trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale); +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +// clang-format off +static const JNINativeMethod gNetworkUtilMethods[] = { + /* name, signature, funcPtr */ + { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork }, + { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess }, + { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, + { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork }, + { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter }, + { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter }, + { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow }, + { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend }, + { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery }, + { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, + { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, + { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork }, +}; +// clang-format on + +int register_android_net_NetworkUtils(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, NETUTILS_PKG_NAME, gNetworkUtilMethods, + NELEM(gNetworkUtilMethods)); +} + +}; // namespace android diff --git a/framework/jni/onload.cpp b/framework/jni/onload.cpp new file mode 100644 index 0000000000..435f4343ed --- /dev/null +++ b/framework/jni/onload.cpp @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#include +#include + +namespace android { + +int register_android_net_NetworkUtils(JNIEnv* env); + +extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + ALOGE("GetEnv failed"); + return JNI_ERR; + } + + if (register_android_net_NetworkUtils(env) < 0) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +} + +}; \ No newline at end of file From 62c5136e006ab1d23d52ed820dbd05d81acc073a Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 19 Mar 2021 13:37:40 +0000 Subject: [PATCH 203/232] Use jniThrowErrnoException from JNIHelp Use the jniThrowErrnoException that is available in JNIHelp, instead of reimplementing in NetworkUtils. Bug: 179229316 Test: device boots and has connectivity Merged-In: I257a9d55ce1f5a7c588e209b4a89d3e7a3e09994 Change-Id: I257a9d55ce1f5a7c588e209b4a89d3e7a3e09994 --- framework/jni/android_net_NetworkUtils.cpp | 44 ++++------------------ 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp index c70309f6ce..48e262a6b1 100644 --- a/framework/jni/android_net_NetworkUtils.cpp +++ b/framework/jni/android_net_NetworkUtils.cpp @@ -30,6 +30,7 @@ #include // NETID_USE_LOCAL_NAMESERVERS #include +#include #include #include #include @@ -57,14 +58,6 @@ static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) { return clazz; } -static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name, - const char* method_signature) { - jmethodID res = env->GetMethodID(clazz, method_name, method_signature); - LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name, - method_signature); - return res; -} - template static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { jobject res = env->NewGlobalRef(in); @@ -72,27 +65,6 @@ static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) { return static_cast(res); } -static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { - ScopedLocalRef detailMessage(env, env->NewStringUTF(functionName)); - if (detailMessage.get() == NULL) { - // Not really much we can do here. We're probably dead in the water, - // but let's try to stumble on... - env->ExceptionClear(); - } - static jclass errnoExceptionClass = - MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException")); - - static jmethodID errnoExceptionCtor = - GetMethodIDOrDie(env, errnoExceptionClass, - "", "(Ljava/lang/String;I)V"); - - jobject exception = env->NewObject(errnoExceptionClass, - errnoExceptionCtor, - detailMessage.get(), - error); - env->Throw(reinterpret_cast(exception)); -} - static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) { struct sock_filter filter_code[] = { @@ -165,7 +137,7 @@ static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags); if (fd < 0) { - throwErrnoException(env, "resNetworkQuery", -fd); + jniThrowErrnoException(env, "resNetworkQuery", -fd); return nullptr; } @@ -180,7 +152,7 @@ static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint int fd = resNetworkSend(netId, data, msgLen, flags); if (fd < 0) { - throwErrnoException(env, "resNetworkSend", -fd); + jniThrowErrnoException(env, "resNetworkSend", -fd); return nullptr; } @@ -195,13 +167,13 @@ static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, job int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE); jniSetFileDescriptorOfFD(env, javaFd, -1); if (res < 0) { - throwErrnoException(env, "resNetworkResult", -res); + jniThrowErrnoException(env, "resNetworkResult", -res); return nullptr; } jbyteArray answer = env->NewByteArray(res); if (answer == nullptr) { - throwErrnoException(env, "resNetworkResult", ENOMEM); + jniThrowErrnoException(env, "resNetworkResult", ENOMEM); return nullptr; } else { env->SetByteArrayRegion(answer, 0, res, @@ -223,7 +195,7 @@ static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobjec static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) { unsigned dnsNetId = 0; if (int res = getNetworkForDns(&dnsNetId) < 0) { - throwErrnoException(env, "getDnsNetId", -res); + jniThrowErrnoException(env, "getDnsNetId", -res); return nullptr; } bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS; @@ -248,7 +220,7 @@ static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, j // Obtain the parameters of the TCP repair window. int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size); if (rc == -1) { - throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno); + jniThrowErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno); return NULL; } @@ -259,7 +231,7 @@ static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, j // should be applied to the window size. rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size); if (rc == -1) { - throwErrnoException(env, "getsockopt : TCP_INFO", errno); + jniThrowErrnoException(env, "getsockopt : TCP_INFO", errno); return NULL; } From 69afcd51dbe9e8187887b0659d01e4f34b9db767 Mon Sep 17 00:00:00 2001 From: paulhu Date: Tue, 27 Apr 2021 00:14:47 +0800 Subject: [PATCH 204/232] Add RESTRICTED_ALLOWED_APPS setting This setting is OEM upstream requirement for third party apps using restricted networks. Bug: 185149952 Test: atests FrameworksNetTests Change-Id: I5e16b46cf2935f38ee1e516bb8b85fa487cf9f61 --- framework/api/module-lib-current.txt | 2 + .../net/ConnectivitySettingsManager.java | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 78dff21b6c..524db18005 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -65,6 +65,7 @@ package android.net { method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context); method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context); method public static int getPrivateDnsMode(@NonNull android.content.Context); + method @NonNull public static java.util.Set getRestrictedAllowedApps(@NonNull android.content.Context); method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean); method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String); @@ -84,6 +85,7 @@ package android.net { method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull int); method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String); method public static void setPrivateDnsMode(@NonNull android.content.Context, int); + method public static void setRestrictedAllowedApps(@NonNull android.content.Context, @NonNull java.util.Set); method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean); method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2 diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index 31e1fb0581..c4bb2d3f54 100644 --- a/framework/src/android/net/ConnectivitySettingsManager.java +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -30,6 +30,7 @@ import android.content.Context; import android.net.ConnectivityManager.MultipathPreference; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Range; import com.android.net.module.util.ProxyUtils; @@ -38,6 +39,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.Duration; import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import java.util.regex.Pattern; /** * A manager class for connectivity module settings. @@ -368,6 +372,13 @@ public class ConnectivitySettingsManager { private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING = "opportunistic"; private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname"; + /** + * A list of apps that should be granted netd system permission for using restricted networks. + * + * @hide + */ + public static final String RESTRICTED_ALLOWED_APPS = "restricted_allowed_apps"; + /** * Get mobile data activity timeout from {@link Settings}. * @@ -1014,4 +1025,47 @@ public class ConnectivitySettingsManager { public static void setMobileDataPreferredApps(@NonNull Context context, @Nullable String list) { Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS, list); } + + /** + * Get the list of apps(from {@link Settings}) that should be granted netd system permission for + * using restricted networks. + * + * @param context The {@link Context} to query the setting. + * @return A list of apps that should be granted netd system permission for using restricted + * networks or null if no setting value. + */ + @NonNull + public static Set getRestrictedAllowedApps(@NonNull Context context) { + final String appList = Settings.Secure.getString( + context.getContentResolver(), RESTRICTED_ALLOWED_APPS); + if (TextUtils.isEmpty(appList)) { + return new ArraySet<>(); + } + return new ArraySet<>(appList.split(";")); + } + + /** + * Set the list of apps(from {@link Settings}) that should be granted netd system permission for + * using restricted networks. + * + * Note: Please refer to android developer guidelines for valid app(package name). + * https://developer.android.com/guide/topics/manifest/manifest-element.html#package + * + * @param context The {@link Context} to set the setting. + * @param list A list of apps that should be granted netd system permission for using + * restricted networks. + */ + public static void setRestrictedAllowedApps(@NonNull Context context, + @NonNull Set list) { + final Pattern appPattern = Pattern.compile("[a-zA-Z_0-9]+([.][a-zA-Z_0-9]+)*"); + final StringJoiner joiner = new StringJoiner(";"); + for (String app : list) { + if (!appPattern.matcher(app).matches()) { + throw new IllegalArgumentException("Invalid app(package name)"); + } + joiner.add(app); + } + Settings.Secure.putString( + context.getContentResolver(), RESTRICTED_ALLOWED_APPS, joiner.toString()); + } } From 31022d6cda18cda46b4e32ac507d8c48a933161d Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Tue, 11 May 2021 13:37:06 +0000 Subject: [PATCH 205/232] Move net unit tests to packages/Connectivity Move the tests together with packages/Connectivity code, so both can be moved to packages/modules/Connectivity together. Also reorganize unit tests in a unit/ directory, as other tests (integration/, common/ etc.) have been added in tests/net since they were created. This makes the directory structure consistent. Test: atest FrameworksNetTests Bug: 187814163 Merged-In: I254ffd1c08ec058d594b4ea55cbae5505f8497cc Change-Id: I254ffd1c08ec058d594b4ea55cbae5505f8497cc --- tests/OWNERS | 8 + tests/TEST_MAPPING | 34 + tests/common/Android.bp | 44 + tests/common/java/ParseExceptionTest.kt | 52 + .../java/android/net/CaptivePortalDataTest.kt | 190 + .../java/android/net/CaptivePortalTest.java | 121 + .../java/android/net/DependenciesTest.java | 113 + .../common/java/android/net/DhcpInfoTest.java | 112 + .../common/java/android/net/IpPrefixTest.java | 374 + .../android/net/KeepalivePacketDataTest.kt | 120 + .../java/android/net/LinkAddressTest.java | 518 + .../java/android/net/LinkPropertiesTest.java | 1271 ++ .../net/MatchAllNetworkSpecifierTest.kt | 75 + .../net/NattKeepalivePacketDataTest.kt | 114 + .../android/net/NetworkAgentConfigTest.kt | 91 + .../android/net/NetworkCapabilitiesTest.java | 1152 ++ .../java/android/net/NetworkProviderTest.kt | 201 + .../java/android/net/NetworkSpecifierTest.kt | 67 + .../java/android/net/NetworkStackTest.java | 51 + .../android/net/NetworkStateSnapshotTest.kt | 73 + .../common/java/android/net/NetworkTest.java | 160 + .../net/OemNetworkPreferencesTest.java | 152 + .../java/android/net/RouteInfoTest.java | 434 + .../net/StaticIpConfigurationTest.java | 269 + .../android/net/TcpKeepalivePacketDataTest.kt | 106 + .../common/java/android/net/UidRangeTest.java | 113 + .../android/net/UnderlyingNetworkInfoTest.kt | 50 + .../android/net/apf/ApfCapabilitiesTest.java | 99 + .../net/metrics/ApfProgramEventTest.kt | 72 + .../java/android/net/metrics/ApfStatsTest.kt | 57 + .../net/metrics/DhcpClientEventTest.kt | 43 + .../android/net/metrics/DhcpErrorEventTest.kt | 65 + .../net/metrics/IpConnectivityLogTest.java | 161 + .../android/net/metrics/IpManagerEventTest.kt | 39 + .../net/metrics/IpReachabilityEventTest.kt | 38 + .../android/net/metrics/NetworkEventTest.kt | 43 + .../java/android/net/metrics/RaEventTest.kt | 72 + .../net/metrics/ValidationProbeEventTest.kt | 72 + .../net/netstats/NetworkStatsApiTest.kt | 209 + .../java/android/net/util/SocketUtilsTest.kt | 90 + tests/deflake/Android.bp | 39 + .../server/net/FrameworksNetDeflakeTest.kt | 28 + tests/integration/Android.bp | 76 + tests/integration/AndroidManifest.xml | 73 + tests/integration/res/values/config.xml | 15 + .../src/android/net/TestNetworkStackClient.kt | 80 + .../ConnectivityServiceIntegrationTest.kt | 258 + .../net/integrationtests/HttpResponse.aidl | 19 + .../net/integrationtests/HttpResponse.kt | 49 + .../INetworkStackInstrumentation.aidl | 25 + .../NetworkStackInstrumentationService.kt | 84 + .../TestNetworkStackService.kt | 100 + .../server/ConnectivityServiceTestUtils.kt | 43 + .../android/server/NetworkAgentWrapper.java | 377 + .../com/android/server/TestNetIdManager.kt | 39 + tests/smoketest/Android.bp | 31 + tests/smoketest/AndroidManifest.xml | 27 + tests/smoketest/AndroidTest.xml | 28 + tests/smoketest/java/SmokeTest.java | 33 + tests/unit/Android.bp | 87 + tests/unit/AndroidManifest.xml | 62 + tests/unit/AndroidTest.xml | 28 + tests/unit/jarjar-rules.txt | 2 + .../app/usage/NetworkStatsManagerTest.java | 212 + .../ConnectivityDiagnosticsManagerTest.java | 384 + .../android/net/ConnectivityManagerTest.java | 430 + .../java/android/net/Ikev2VpnProfileTest.java | 439 + .../java/android/net/IpMemoryStoreTest.java | 332 + .../java/android/net/IpSecAlgorithmTest.java | 226 + .../java/android/net/IpSecConfigTest.java | 103 + .../java/android/net/IpSecManagerTest.java | 305 + .../java/android/net/IpSecTransformTest.java | 62 + .../net/KeepalivePacketDataUtilTest.java | 205 + .../unit/java/android/net/MacAddressTest.java | 312 + .../java/android/net/NetworkIdentityTest.kt | 54 + .../android/net/NetworkStatsHistoryTest.java | 599 + .../java/android/net/NetworkStatsTest.java | 1024 ++ .../java/android/net/NetworkTemplateTest.kt | 351 + .../java/android/net/NetworkUtilsTest.java | 128 + .../java/android/net/QosSocketFilterTest.java | 75 + .../net/TelephonyNetworkSpecifierTest.java | 113 + .../unit/java/android/net/VpnManagerTest.java | 138 + .../android/net/VpnTransportInfoTest.java | 68 + .../net/ipmemorystore/ParcelableTests.java | 142 + .../java/android/net/nsd/NsdManagerTest.java | 388 + .../android/net/nsd/NsdServiceInfoTest.java | 188 + .../java/android/net/util/DnsUtilsTest.java | 216 + .../android/net/util/KeepaliveUtilsTest.kt | 145 + .../net/util/MultinetworkPolicyTrackerTest.kt | 148 + .../net/NetworkUtilsInternalTest.java | 89 + .../android/internal/net/VpnProfileTest.java | 218 + .../android/internal/util/BitUtilsTest.java | 201 + .../android/internal/util/RingBufferTest.java | 178 + .../server/ConnectivityServiceTest.java | 12461 ++++++++++++++++ .../server/IpSecServiceParameterizedTest.java | 1004 ++ .../IpSecServiceRefcountedResourceTest.java | 358 + .../com/android/server/IpSecServiceTest.java | 670 + .../android/server/LegacyTypeTrackerTest.kt | 197 + .../com/android/server/NetIdManagerTest.kt | 53 + .../server/NetworkManagementServiceTest.java | 315 + .../com/android/server/NsdServiceTest.java | 194 + .../server/connectivity/DnsManagerTest.java | 433 + .../server/connectivity/FullScoreTest.kt | 134 + .../IpConnectivityEventBuilderTest.java | 561 + .../IpConnectivityMetricsTest.java | 645 + .../connectivity/LingerMonitorTest.java | 393 + .../server/connectivity/MetricsTestUtil.java | 81 + .../MultipathPolicyTrackerTest.java | 381 + .../server/connectivity/Nat464XlatTest.java | 555 + .../NetdEventListenerServiceTest.java | 554 + .../NetworkNotificationManagerTest.java | 295 + .../server/connectivity/NetworkRankerTest.kt | 84 + .../connectivity/PermissionMonitorTest.java | 803 + .../android/server/connectivity/VpnTest.java | 1283 ++ .../server/net/NetworkStatsAccessTest.java | 189 + .../server/net/NetworkStatsBaseTest.java | 119 + .../net/NetworkStatsCollectionTest.java | 594 + .../server/net/NetworkStatsFactoryTest.java | 578 + .../server/net/NetworkStatsObserversTest.java | 447 + .../server/net/NetworkStatsServiceTest.java | 1767 +++ .../NetworkStatsSubscriptionsMonitorTest.java | 386 + .../ipmemorystore/NetworkAttributesTest.java | 70 + tests/unit/jni/Android.bp | 32 + tests/unit/jni/test_onload.cpp | 44 + tests/unit/res/raw/history_v1 | Bin 0 -> 144 bytes tests/unit/res/raw/net_dev_typical | 8 + tests/unit/res/raw/netstats_uid_v4 | Bin 0 -> 156516 bytes tests/unit/res/raw/netstats_v1 | Bin 0 -> 18742 bytes .../unit/res/raw/xt_qtaguid_iface_fmt_typical | 4 + tests/unit/res/raw/xt_qtaguid_iface_typical | 6 + tests/unit/res/raw/xt_qtaguid_typical | 71 + .../res/raw/xt_qtaguid_vpn_incorrect_iface | 3 + .../res/raw/xt_qtaguid_vpn_one_underlying | 5 + .../xt_qtaguid_vpn_one_underlying_compression | 4 + .../xt_qtaguid_vpn_one_underlying_own_traffic | 6 + .../raw/xt_qtaguid_vpn_one_underlying_two_vpn | 9 + .../raw/xt_qtaguid_vpn_rewrite_through_self | 6 + .../xt_qtaguid_vpn_two_underlying_duplication | 5 + .../raw/xt_qtaguid_vpn_two_underlying_split | 4 + ...aguid_vpn_two_underlying_split_compression | 4 + tests/unit/res/raw/xt_qtaguid_vpn_with_clat | 8 + tests/unit/res/raw/xt_qtaguid_with_clat | 43 + .../xt_qtaguid_with_clat_100mb_download_after | 189 + ...xt_qtaguid_with_clat_100mb_download_before | 187 + .../unit/res/raw/xt_qtaguid_with_clat_simple | 4 + 145 files changed, 42447 insertions(+) create mode 100644 tests/OWNERS create mode 100644 tests/TEST_MAPPING create mode 100644 tests/common/Android.bp create mode 100644 tests/common/java/ParseExceptionTest.kt create mode 100644 tests/common/java/android/net/CaptivePortalDataTest.kt create mode 100644 tests/common/java/android/net/CaptivePortalTest.java create mode 100644 tests/common/java/android/net/DependenciesTest.java create mode 100644 tests/common/java/android/net/DhcpInfoTest.java create mode 100644 tests/common/java/android/net/IpPrefixTest.java create mode 100644 tests/common/java/android/net/KeepalivePacketDataTest.kt create mode 100644 tests/common/java/android/net/LinkAddressTest.java create mode 100644 tests/common/java/android/net/LinkPropertiesTest.java create mode 100644 tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt create mode 100644 tests/common/java/android/net/NattKeepalivePacketDataTest.kt create mode 100644 tests/common/java/android/net/NetworkAgentConfigTest.kt create mode 100644 tests/common/java/android/net/NetworkCapabilitiesTest.java create mode 100644 tests/common/java/android/net/NetworkProviderTest.kt create mode 100644 tests/common/java/android/net/NetworkSpecifierTest.kt create mode 100644 tests/common/java/android/net/NetworkStackTest.java create mode 100644 tests/common/java/android/net/NetworkStateSnapshotTest.kt create mode 100644 tests/common/java/android/net/NetworkTest.java create mode 100644 tests/common/java/android/net/OemNetworkPreferencesTest.java create mode 100644 tests/common/java/android/net/RouteInfoTest.java create mode 100644 tests/common/java/android/net/StaticIpConfigurationTest.java create mode 100644 tests/common/java/android/net/TcpKeepalivePacketDataTest.kt create mode 100644 tests/common/java/android/net/UidRangeTest.java create mode 100644 tests/common/java/android/net/UnderlyingNetworkInfoTest.kt create mode 100644 tests/common/java/android/net/apf/ApfCapabilitiesTest.java create mode 100644 tests/common/java/android/net/metrics/ApfProgramEventTest.kt create mode 100644 tests/common/java/android/net/metrics/ApfStatsTest.kt create mode 100644 tests/common/java/android/net/metrics/DhcpClientEventTest.kt create mode 100644 tests/common/java/android/net/metrics/DhcpErrorEventTest.kt create mode 100644 tests/common/java/android/net/metrics/IpConnectivityLogTest.java create mode 100644 tests/common/java/android/net/metrics/IpManagerEventTest.kt create mode 100644 tests/common/java/android/net/metrics/IpReachabilityEventTest.kt create mode 100644 tests/common/java/android/net/metrics/NetworkEventTest.kt create mode 100644 tests/common/java/android/net/metrics/RaEventTest.kt create mode 100644 tests/common/java/android/net/metrics/ValidationProbeEventTest.kt create mode 100644 tests/common/java/android/net/netstats/NetworkStatsApiTest.kt create mode 100644 tests/common/java/android/net/util/SocketUtilsTest.kt create mode 100644 tests/deflake/Android.bp create mode 100644 tests/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt create mode 100644 tests/integration/Android.bp create mode 100644 tests/integration/AndroidManifest.xml create mode 100644 tests/integration/res/values/config.xml create mode 100644 tests/integration/src/android/net/TestNetworkStackClient.kt create mode 100644 tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt create mode 100644 tests/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl create mode 100644 tests/integration/src/com/android/server/net/integrationtests/HttpResponse.kt create mode 100644 tests/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl create mode 100644 tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt create mode 100644 tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt create mode 100644 tests/integration/util/com/android/server/ConnectivityServiceTestUtils.kt create mode 100644 tests/integration/util/com/android/server/NetworkAgentWrapper.java create mode 100644 tests/integration/util/com/android/server/TestNetIdManager.kt create mode 100644 tests/smoketest/Android.bp create mode 100644 tests/smoketest/AndroidManifest.xml create mode 100644 tests/smoketest/AndroidTest.xml create mode 100644 tests/smoketest/java/SmokeTest.java create mode 100644 tests/unit/Android.bp create mode 100644 tests/unit/AndroidManifest.xml create mode 100644 tests/unit/AndroidTest.xml create mode 100644 tests/unit/jarjar-rules.txt create mode 100644 tests/unit/java/android/app/usage/NetworkStatsManagerTest.java create mode 100644 tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java create mode 100644 tests/unit/java/android/net/ConnectivityManagerTest.java create mode 100644 tests/unit/java/android/net/Ikev2VpnProfileTest.java create mode 100644 tests/unit/java/android/net/IpMemoryStoreTest.java create mode 100644 tests/unit/java/android/net/IpSecAlgorithmTest.java create mode 100644 tests/unit/java/android/net/IpSecConfigTest.java create mode 100644 tests/unit/java/android/net/IpSecManagerTest.java create mode 100644 tests/unit/java/android/net/IpSecTransformTest.java create mode 100644 tests/unit/java/android/net/KeepalivePacketDataUtilTest.java create mode 100644 tests/unit/java/android/net/MacAddressTest.java create mode 100644 tests/unit/java/android/net/NetworkIdentityTest.kt create mode 100644 tests/unit/java/android/net/NetworkStatsHistoryTest.java create mode 100644 tests/unit/java/android/net/NetworkStatsTest.java create mode 100644 tests/unit/java/android/net/NetworkTemplateTest.kt create mode 100644 tests/unit/java/android/net/NetworkUtilsTest.java create mode 100644 tests/unit/java/android/net/QosSocketFilterTest.java create mode 100644 tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java create mode 100644 tests/unit/java/android/net/VpnManagerTest.java create mode 100644 tests/unit/java/android/net/VpnTransportInfoTest.java create mode 100644 tests/unit/java/android/net/ipmemorystore/ParcelableTests.java create mode 100644 tests/unit/java/android/net/nsd/NsdManagerTest.java create mode 100644 tests/unit/java/android/net/nsd/NsdServiceInfoTest.java create mode 100644 tests/unit/java/android/net/util/DnsUtilsTest.java create mode 100644 tests/unit/java/android/net/util/KeepaliveUtilsTest.kt create mode 100644 tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt create mode 100644 tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java create mode 100644 tests/unit/java/com/android/internal/net/VpnProfileTest.java create mode 100644 tests/unit/java/com/android/internal/util/BitUtilsTest.java create mode 100644 tests/unit/java/com/android/internal/util/RingBufferTest.java create mode 100644 tests/unit/java/com/android/server/ConnectivityServiceTest.java create mode 100644 tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java create mode 100644 tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java create mode 100644 tests/unit/java/com/android/server/IpSecServiceTest.java create mode 100644 tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt create mode 100644 tests/unit/java/com/android/server/NetIdManagerTest.kt create mode 100644 tests/unit/java/com/android/server/NetworkManagementServiceTest.java create mode 100644 tests/unit/java/com/android/server/NsdServiceTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/DnsManagerTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/FullScoreTest.kt create mode 100644 tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/MetricsTestUtil.java create mode 100644 tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt create mode 100644 tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java create mode 100644 tests/unit/java/com/android/server/connectivity/VpnTest.java create mode 100644 tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java create mode 100644 tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java create mode 100644 tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java create mode 100644 tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java create mode 100644 tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java create mode 100644 tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java create mode 100644 tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java create mode 100644 tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java create mode 100644 tests/unit/jni/Android.bp create mode 100644 tests/unit/jni/test_onload.cpp create mode 100644 tests/unit/res/raw/history_v1 create mode 100644 tests/unit/res/raw/net_dev_typical create mode 100644 tests/unit/res/raw/netstats_uid_v4 create mode 100644 tests/unit/res/raw/netstats_v1 create mode 100644 tests/unit/res/raw/xt_qtaguid_iface_fmt_typical create mode 100644 tests/unit/res/raw/xt_qtaguid_iface_typical create mode 100644 tests/unit/res/raw/xt_qtaguid_typical create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_incorrect_iface create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_one_underlying create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_one_underlying_compression create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_one_underlying_own_traffic create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_one_underlying_two_vpn create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_rewrite_through_self create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_two_underlying_duplication create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_two_underlying_split create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_two_underlying_split_compression create mode 100644 tests/unit/res/raw/xt_qtaguid_vpn_with_clat create mode 100644 tests/unit/res/raw/xt_qtaguid_with_clat create mode 100644 tests/unit/res/raw/xt_qtaguid_with_clat_100mb_download_after create mode 100644 tests/unit/res/raw/xt_qtaguid_with_clat_100mb_download_before create mode 100644 tests/unit/res/raw/xt_qtaguid_with_clat_simple diff --git a/tests/OWNERS b/tests/OWNERS new file mode 100644 index 0000000000..d3836d4c6c --- /dev/null +++ b/tests/OWNERS @@ -0,0 +1,8 @@ +set noparent + +codewiz@google.com +jchalard@google.com +junyulai@google.com +lorenzo@google.com +reminv@google.com +satk@google.com diff --git a/tests/TEST_MAPPING b/tests/TEST_MAPPING new file mode 100644 index 0000000000..502f885ceb --- /dev/null +++ b/tests/TEST_MAPPING @@ -0,0 +1,34 @@ +{ + "presubmit": [ + { + "name": "FrameworksNetIntegrationTests" + } + ], + "postsubmit": [ + { + "name": "FrameworksNetDeflakeTest" + } + ], + "auto-postsubmit": [ + // Test tag for automotive targets. These are only running in postsubmit so as to harden the + // automotive targets to avoid introducing additional test flake and build time. The plan for + // presubmit testing for auto is to augment the existing tests to cover auto use cases as well. + // Additionally, this tag is used in targeted test suites to limit resource usage on the test + // infra during the hardening phase. + // TODO: this tag to be removed once the above is no longer an issue. + { + "name": "FrameworksNetTests" + }, + { + "name": "FrameworksNetIntegrationTests" + }, + { + "name": "FrameworksNetDeflakeTest" + } + ], + "imports": [ + { + "path": "packages/modules/Connectivity" + } + ] +} \ No newline at end of file diff --git a/tests/common/Android.bp b/tests/common/Android.bp new file mode 100644 index 0000000000..babb81c5fa --- /dev/null +++ b/tests/common/Android.bp @@ -0,0 +1,44 @@ +// +// 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. +// + +// Tests in this folder are included both in unit tests and CTS. +// They must be fast and stable, and exercise public or test APIs. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library { + name: "FrameworksNetCommonTests", + srcs: ["java/**/*.java", "java/**/*.kt"], + static_libs: [ + "androidx.core_core", + "androidx.test.rules", + "junit", + "mockito-target-minus-junit4", + "modules-utils-build", + "net-tests-utils", + "net-utils-framework-common", + "platform-test-annotations", + ], + libs: [ + "android.test.base.stubs", + ], +} diff --git a/tests/common/java/ParseExceptionTest.kt b/tests/common/java/ParseExceptionTest.kt new file mode 100644 index 0000000000..b702d61a9f --- /dev/null +++ b/tests/common/java/ParseExceptionTest.kt @@ -0,0 +1,52 @@ +/* + * 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. + */ + +import android.net.ParseException +import android.os.Build +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertNull +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ParseExceptionTest { + @get:Rule + val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.R) + + @Test + fun testConstructor_WithCause() { + val testMessage = "Test message" + val base = Exception("Test") + val exception = ParseException(testMessage, base) + + assertEquals(testMessage, exception.response) + assertEquals(base, exception.cause) + } + + @Test + fun testConstructor_NoCause() { + val testMessage = "Test message" + val exception = ParseException(testMessage) + + assertEquals(testMessage, exception.response) + assertNull(exception.cause) + } +} \ No newline at end of file diff --git a/tests/common/java/android/net/CaptivePortalDataTest.kt b/tests/common/java/android/net/CaptivePortalDataTest.kt new file mode 100644 index 0000000000..18a93319b2 --- /dev/null +++ b/tests/common/java/android/net/CaptivePortalDataTest.kt @@ -0,0 +1,190 @@ +/* + * 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 + +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.modules.utils.build.SdkLevel +import com.android.testutils.assertParcelSane +import com.android.testutils.assertParcelingIsLossless +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +@SmallTest +@RunWith(DevSdkIgnoreRunner::class) +@IgnoreUpTo(Build.VERSION_CODES.Q) +class CaptivePortalDataTest { + @Rule @JvmField + val ignoreRule = DevSdkIgnoreRule() + + private val data = CaptivePortalData.Builder() + .setRefreshTime(123L) + .setUserPortalUrl(Uri.parse("https://portal.example.com/test")) + .setVenueInfoUrl(Uri.parse("https://venue.example.com/test")) + .setSessionExtendable(true) + .setBytesRemaining(456L) + .setExpiryTime(789L) + .setCaptive(true) + .apply { + if (SdkLevel.isAtLeastS()) { + setVenueFriendlyName("venue friendly name") + } + } + .build() + + private val dataFromPasspoint = CaptivePortalData.Builder() + .setCaptive(true) + .apply { + if (SdkLevel.isAtLeastS()) { + setVenueFriendlyName("venue friendly name") + setUserPortalUrl(Uri.parse("https://tc.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + setVenueInfoUrl(Uri.parse("https://venue.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + } + } + .build() + + private fun makeBuilder() = CaptivePortalData.Builder(data) + + @Test + fun testParcelUnparcel() { + val fieldCount = if (SdkLevel.isAtLeastS()) 10 else 7 + assertParcelSane(data, fieldCount) + assertParcelSane(dataFromPasspoint, fieldCount) + + assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build()) + assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build()) + } + + @Test + fun testEquals() { + assertEquals(data, makeBuilder().build()) + + assertNotEqualsAfterChange { it.setRefreshTime(456L) } + assertNotEqualsAfterChange { it.setUserPortalUrl(Uri.parse("https://example.com/")) } + assertNotEqualsAfterChange { it.setUserPortalUrl(null) } + assertNotEqualsAfterChange { it.setVenueInfoUrl(Uri.parse("https://example.com/")) } + assertNotEqualsAfterChange { it.setVenueInfoUrl(null) } + assertNotEqualsAfterChange { it.setSessionExtendable(false) } + assertNotEqualsAfterChange { it.setBytesRemaining(789L) } + assertNotEqualsAfterChange { it.setExpiryTime(12L) } + assertNotEqualsAfterChange { it.setCaptive(false) } + + if (SdkLevel.isAtLeastS()) { + assertNotEqualsAfterChange { it.setVenueFriendlyName("another friendly name") } + assertNotEqualsAfterChange { it.setVenueFriendlyName(null) } + + assertEquals(dataFromPasspoint, CaptivePortalData.Builder(dataFromPasspoint).build()) + assertNotEqualsAfterChange { it.setUserPortalUrl( + Uri.parse("https://tc.example.com/passpoint")) } + assertNotEqualsAfterChange { it.setUserPortalUrl( + Uri.parse("https://tc.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) } + assertNotEqualsAfterChange { it.setUserPortalUrl( + Uri.parse("https://tc.example.com/other"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) } + assertNotEqualsAfterChange { it.setUserPortalUrl( + Uri.parse("https://tc.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) } + assertNotEqualsAfterChange { it.setVenueInfoUrl( + Uri.parse("https://venue.example.com/passpoint")) } + assertNotEqualsAfterChange { it.setVenueInfoUrl( + Uri.parse("https://venue.example.com/other"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) } + assertNotEqualsAfterChange { it.setVenueInfoUrl( + Uri.parse("https://venue.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) } + } + } + + @Test + fun testUserPortalUrl() { + assertEquals(Uri.parse("https://portal.example.com/test"), data.userPortalUrl) + } + + @Test + fun testVenueInfoUrl() { + assertEquals(Uri.parse("https://venue.example.com/test"), data.venueInfoUrl) + } + + @Test + fun testIsSessionExtendable() { + assertTrue(data.isSessionExtendable) + } + + @Test + fun testByteLimit() { + assertEquals(456L, data.byteLimit) + // Test byteLimit unset. + assertEquals(-1L, CaptivePortalData.Builder(null).build().byteLimit) + } + + @Test + fun testRefreshTimeMillis() { + assertEquals(123L, data.refreshTimeMillis) + } + + @Test + fun testExpiryTimeMillis() { + assertEquals(789L, data.expiryTimeMillis) + // Test expiryTimeMillis unset. + assertEquals(-1L, CaptivePortalData.Builder(null).build().expiryTimeMillis) + } + + @Test + fun testIsCaptive() { + assertTrue(data.isCaptive) + assertFalse(makeBuilder().setCaptive(false).build().isCaptive) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + fun testVenueFriendlyName() { + assertEquals("venue friendly name", data.venueFriendlyName) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + fun testGetVenueInfoUrlSource() { + assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER, + data.venueInfoUrlSource) + assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT, + dataFromPasspoint.venueInfoUrlSource) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + fun testGetUserPortalUrlSource() { + assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER, + data.userPortalUrlSource) + assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT, + dataFromPasspoint.userPortalUrlSource) + } + + private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) = + CaptivePortalData.Builder(this).apply { mutator(this) }.build() + + private fun assertNotEqualsAfterChange(mutator: (CaptivePortalData.Builder) -> Unit) { + assertNotEquals(data, data.mutate(mutator)) + } +} \ No newline at end of file diff --git a/tests/common/java/android/net/CaptivePortalTest.java b/tests/common/java/android/net/CaptivePortalTest.java new file mode 100644 index 0000000000..15d3398d43 --- /dev/null +++ b/tests/common/java/android/net/CaptivePortalTest.java @@ -0,0 +1,121 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; + +import android.os.Build; +import android.os.RemoteException; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class CaptivePortalTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + + private static final int DEFAULT_TIMEOUT_MS = 5000; + private static final String TEST_PACKAGE_NAME = "com.google.android.test"; + + private final class MyCaptivePortalImpl extends ICaptivePortal.Stub { + int mCode = -1; + String mPackageName = null; + + @Override + public void appResponse(final int response) throws RemoteException { + mCode = response; + } + + @Override + public void appRequest(final int request) throws RemoteException { + mCode = request; + } + + // This is only @Override on R- + public void logEvent(int eventId, String packageName) throws RemoteException { + mCode = eventId; + mPackageName = packageName; + } + } + + private interface TestFunctor { + void useCaptivePortal(CaptivePortal o); + } + + private MyCaptivePortalImpl runCaptivePortalTest(TestFunctor f) { + final MyCaptivePortalImpl cp = new MyCaptivePortalImpl(); + f.useCaptivePortal(new CaptivePortal(cp.asBinder())); + return cp; + } + + @Test + public void testReportCaptivePortalDismissed() { + final MyCaptivePortalImpl result = + runCaptivePortalTest(c -> c.reportCaptivePortalDismissed()); + assertEquals(result.mCode, CaptivePortal.APP_RETURN_DISMISSED); + } + + @Test + public void testIgnoreNetwork() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.ignoreNetwork()); + assertEquals(result.mCode, CaptivePortal.APP_RETURN_UNWANTED); + } + + @Test + public void testUseNetwork() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.useNetwork()); + assertEquals(result.mCode, CaptivePortal.APP_RETURN_WANTED_AS_IS); + } + + @IgnoreUpTo(Build.VERSION_CODES.Q) + @Test + public void testReevaluateNetwork() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork()); + assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED); + } + + @IgnoreUpTo(Build.VERSION_CODES.R) + @Test + public void testLogEvent() { + /** + * From S testLogEvent is expected to do nothing but shouldn't crash (the API + * logEvent has been deprecated). + */ + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( + 0, + TEST_PACKAGE_NAME)); + } + + @IgnoreAfter(Build.VERSION_CODES.R) + @Test + public void testLogEvent_UntilR() { + final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent( + 42, TEST_PACKAGE_NAME)); + assertEquals(result.mCode, 42); + assertEquals(result.mPackageName, TEST_PACKAGE_NAME); + } +} diff --git a/tests/common/java/android/net/DependenciesTest.java b/tests/common/java/android/net/DependenciesTest.java new file mode 100644 index 0000000000..ac1c28a454 --- /dev/null +++ b/tests/common/java/android/net/DependenciesTest.java @@ -0,0 +1,113 @@ +/* + * 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 android.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +/** + * A simple class that tests dependencies to java standard tools from the + * Network stack. These tests are not meant to be comprehensive tests of + * the relevant APIs : such tests belong in the relevant test suite for + * these dependencies. Instead, this just makes sure coverage is present + * by calling the methods in the exact way (or a representative way of how) + * they are called in the network stack. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DependenciesTest { + // Used to in ipmemorystore's RegularMaintenanceJobService to convert + // 24 hours into seconds + @Test + public void testTimeUnit() { + final int hours = 24; + final long inSeconds = TimeUnit.HOURS.toMillis(hours); + assertEquals(inSeconds, hours * 60 * 60 * 1000); + } + + private byte[] makeTrivialArray(final int size) { + final byte[] src = new byte[size]; + for (int i = 0; i < size; ++i) { + src[i] = (byte) i; + } + return src; + } + + // Used in ApfFilter to find an IP address from a byte array + @Test + public void testArrays() { + final int size = 128; + final byte[] src = makeTrivialArray(size); + + // Test copy + final int copySize = 16; + final int offset = 24; + final byte[] expected = new byte[copySize]; + for (int i = 0; i < copySize; ++i) { + expected[i] = (byte) (offset + i); + } + + final byte[] copy = Arrays.copyOfRange(src, offset, offset + copySize); + assertArrayEquals(expected, copy); + assertArrayEquals(new byte[0], Arrays.copyOfRange(src, size, size)); + } + + // Used mainly in the Dhcp code + @Test + public void testCopyOf() { + final byte[] src = makeTrivialArray(128); + final byte[] copy = Arrays.copyOf(src, src.length); + assertArrayEquals(src, copy); + assertFalse(src == copy); + + assertArrayEquals(new byte[0], Arrays.copyOf(src, 0)); + + final int excess = 16; + final byte[] biggerCopy = Arrays.copyOf(src, src.length + excess); + for (int i = src.length; i < src.length + excess; ++i) { + assertEquals(0, biggerCopy[i]); + } + for (int i = src.length - 1; i >= 0; --i) { + assertEquals(src[i], biggerCopy[i]); + } + } + + // Used mainly in DnsUtils but also various other places + @Test + public void testAsList() { + final int size = 24; + final Object[] src = new Object[size]; + final ArrayList expected = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + final Object o = new Object(); + src[i] = o; + expected.add(o); + } + assertEquals(expected, Arrays.asList(src)); + } +} diff --git a/tests/common/java/android/net/DhcpInfoTest.java b/tests/common/java/android/net/DhcpInfoTest.java new file mode 100644 index 0000000000..ab4726bab5 --- /dev/null +++ b/tests/common/java/android/net/DhcpInfoTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2009 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; + +import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTL; +import static com.android.testutils.MiscAsserts.assertFieldCountEquals; +import static com.android.testutils.ParcelUtils.parcelingRoundTrip; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.Nullable; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.InetAddress; + +@RunWith(AndroidJUnit4.class) +public class DhcpInfoTest { + private static final String STR_ADDR1 = "255.255.255.255"; + private static final String STR_ADDR2 = "127.0.0.1"; + private static final String STR_ADDR3 = "192.168.1.1"; + private static final String STR_ADDR4 = "192.168.1.0"; + private static final int LEASE_TIME = 9999; + + private int ipToInteger(String ipString) throws Exception { + return inet4AddressToIntHTL((Inet4Address) InetAddress.getByName(ipString)); + } + + private DhcpInfo createDhcpInfoObject() throws Exception { + final DhcpInfo dhcpInfo = new DhcpInfo(); + dhcpInfo.ipAddress = ipToInteger(STR_ADDR1); + dhcpInfo.gateway = ipToInteger(STR_ADDR2); + dhcpInfo.netmask = ipToInteger(STR_ADDR3); + dhcpInfo.dns1 = ipToInteger(STR_ADDR4); + dhcpInfo.dns2 = ipToInteger(STR_ADDR4); + dhcpInfo.serverAddress = ipToInteger(STR_ADDR2); + dhcpInfo.leaseDuration = LEASE_TIME; + return dhcpInfo; + } + + @Test + public void testConstructor() { + new DhcpInfo(); + } + + @Test + public void testToString() throws Exception { + final String expectedDefault = "ipaddr 0.0.0.0 gateway 0.0.0.0 netmask 0.0.0.0 " + + "dns1 0.0.0.0 dns2 0.0.0.0 DHCP server 0.0.0.0 lease 0 seconds"; + + DhcpInfo dhcpInfo = new DhcpInfo(); + + // Test default string. + assertEquals(expectedDefault, dhcpInfo.toString()); + + dhcpInfo = createDhcpInfoObject(); + + final String expected = "ipaddr " + STR_ADDR1 + " gateway " + STR_ADDR2 + " netmask " + + STR_ADDR3 + " dns1 " + STR_ADDR4 + " dns2 " + STR_ADDR4 + " DHCP server " + + STR_ADDR2 + " lease " + LEASE_TIME + " seconds"; + // Test with new values + assertEquals(expected, dhcpInfo.toString()); + } + + private boolean dhcpInfoEquals(@Nullable DhcpInfo left, @Nullable DhcpInfo right) { + if (left == null && right == null) return true; + + if (left == null || right == null) return false; + + return left.ipAddress == right.ipAddress + && left.gateway == right.gateway + && left.netmask == right.netmask + && left.dns1 == right.dns1 + && left.dns2 == right.dns2 + && left.serverAddress == right.serverAddress + && left.leaseDuration == right.leaseDuration; + } + + @Test + public void testParcelDhcpInfo() throws Exception { + // Cannot use assertParcelSane() here because this requires .equals() to work as + // defined, but DhcpInfo has a different legacy behavior that we cannot change. + final DhcpInfo dhcpInfo = createDhcpInfoObject(); + assertFieldCountEquals(7, DhcpInfo.class); + + final DhcpInfo dhcpInfoRoundTrip = parcelingRoundTrip(dhcpInfo); + assertTrue(dhcpInfoEquals(null, null)); + assertFalse(dhcpInfoEquals(null, dhcpInfoRoundTrip)); + assertFalse(dhcpInfoEquals(dhcpInfo, null)); + assertTrue(dhcpInfoEquals(dhcpInfo, dhcpInfoRoundTrip)); + } +} diff --git a/tests/common/java/android/net/IpPrefixTest.java b/tests/common/java/android/net/IpPrefixTest.java new file mode 100644 index 0000000000..50ecb42835 --- /dev/null +++ b/tests/common/java/android/net/IpPrefixTest.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2014 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; + +import static com.android.testutils.MiscAsserts.assertEqualBothWays; +import static com.android.testutils.MiscAsserts.assertFieldCountEquals; +import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay; +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.Random; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpPrefixTest { + + private static InetAddress address(String addr) { + return InetAddress.parseNumericAddress(addr); + } + + // Explicitly cast everything to byte because "error: possible loss of precision". + private static final byte[] IPV4_BYTES = { (byte) 192, (byte) 0, (byte) 2, (byte) 4}; + private static final byte[] IPV6_BYTES = { + (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef, + (byte) 0x0f, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xa0 + }; + + @Test + public void testConstructor() { + IpPrefix p; + try { + p = new IpPrefix((byte[]) null, 9); + fail("Expected NullPointerException: null byte array"); + } catch (RuntimeException expected) { } + + try { + p = new IpPrefix((InetAddress) null, 10); + fail("Expected NullPointerException: null InetAddress"); + } catch (RuntimeException expected) { } + + try { + p = new IpPrefix((String) null); + fail("Expected NullPointerException: null String"); + } catch (RuntimeException expected) { } + + + try { + byte[] b2 = {1, 2, 3, 4, 5}; + p = new IpPrefix(b2, 29); + fail("Expected IllegalArgumentException: invalid array length"); + } catch (IllegalArgumentException expected) { } + + try { + p = new IpPrefix("1.2.3.4"); + fail("Expected IllegalArgumentException: no prefix length"); + } catch (IllegalArgumentException expected) { } + + try { + p = new IpPrefix("1.2.3.4/"); + fail("Expected IllegalArgumentException: empty prefix length"); + } catch (IllegalArgumentException expected) { } + + try { + p = new IpPrefix("foo/32"); + fail("Expected IllegalArgumentException: invalid address"); + } catch (IllegalArgumentException expected) { } + + try { + p = new IpPrefix("1/32"); + fail("Expected IllegalArgumentException: deprecated IPv4 format"); + } catch (IllegalArgumentException expected) { } + + try { + p = new IpPrefix("1.2.3.256/32"); + fail("Expected IllegalArgumentException: invalid IPv4 address"); + } catch (IllegalArgumentException expected) { } + + try { + p = new IpPrefix("foo/32"); + fail("Expected IllegalArgumentException: non-address"); + } catch (IllegalArgumentException expected) { } + + try { + p = new IpPrefix("f00:::/32"); + fail("Expected IllegalArgumentException: invalid IPv6 address"); + } catch (IllegalArgumentException expected) { } + + p = new IpPrefix("/64"); + assertEquals("::/64", p.toString()); + + p = new IpPrefix("/128"); + assertEquals("::1/128", p.toString()); + + p = new IpPrefix("[2001:db8::123]/64"); + assertEquals("2001:db8::/64", p.toString()); + } + + @Test + public void testTruncation() { + IpPrefix p; + + p = new IpPrefix(IPV4_BYTES, 32); + assertEquals("192.0.2.4/32", p.toString()); + + p = new IpPrefix(IPV4_BYTES, 29); + assertEquals("192.0.2.0/29", p.toString()); + + p = new IpPrefix(IPV4_BYTES, 8); + assertEquals("192.0.0.0/8", p.toString()); + + p = new IpPrefix(IPV4_BYTES, 0); + assertEquals("0.0.0.0/0", p.toString()); + + try { + p = new IpPrefix(IPV4_BYTES, 33); + fail("Expected IllegalArgumentException: invalid prefix length"); + } catch (RuntimeException expected) { } + + try { + p = new IpPrefix(IPV4_BYTES, 128); + fail("Expected IllegalArgumentException: invalid prefix length"); + } catch (RuntimeException expected) { } + + try { + p = new IpPrefix(IPV4_BYTES, -1); + fail("Expected IllegalArgumentException: negative prefix length"); + } catch (RuntimeException expected) { } + + p = new IpPrefix(IPV6_BYTES, 128); + assertEquals("2001:db8:dead:beef:f00::a0/128", p.toString()); + + p = new IpPrefix(IPV6_BYTES, 122); + assertEquals("2001:db8:dead:beef:f00::80/122", p.toString()); + + p = new IpPrefix(IPV6_BYTES, 64); + assertEquals("2001:db8:dead:beef::/64", p.toString()); + + p = new IpPrefix(IPV6_BYTES, 3); + assertEquals("2000::/3", p.toString()); + + p = new IpPrefix(IPV6_BYTES, 0); + assertEquals("::/0", p.toString()); + + try { + p = new IpPrefix(IPV6_BYTES, -1); + fail("Expected IllegalArgumentException: negative prefix length"); + } catch (RuntimeException expected) { } + + try { + p = new IpPrefix(IPV6_BYTES, 129); + fail("Expected IllegalArgumentException: negative prefix length"); + } catch (RuntimeException expected) { } + + } + + @Test + public void testEquals() { + IpPrefix p1, p2; + + p1 = new IpPrefix("192.0.2.251/23"); + p2 = new IpPrefix(new byte[]{(byte) 192, (byte) 0, (byte) 2, (byte) 251}, 23); + assertEqualBothWays(p1, p2); + + p1 = new IpPrefix("192.0.2.5/23"); + assertEqualBothWays(p1, p2); + + p1 = new IpPrefix("192.0.2.5/24"); + assertNotEqualEitherWay(p1, p2); + + p1 = new IpPrefix("192.0.4.5/23"); + assertNotEqualEitherWay(p1, p2); + + + p1 = new IpPrefix("2001:db8:dead:beef:f00::80/122"); + p2 = new IpPrefix(IPV6_BYTES, 122); + assertEquals("2001:db8:dead:beef:f00::80/122", p2.toString()); + assertEqualBothWays(p1, p2); + + p1 = new IpPrefix("2001:db8:dead:beef:f00::bf/122"); + assertEqualBothWays(p1, p2); + + p1 = new IpPrefix("2001:db8:dead:beef:f00::8:0/123"); + assertNotEqualEitherWay(p1, p2); + + p1 = new IpPrefix("2001:db8:dead:beef::/122"); + assertNotEqualEitherWay(p1, p2); + + // 192.0.2.4/32 != c000:0204::/32. + byte[] ipv6bytes = new byte[16]; + System.arraycopy(IPV4_BYTES, 0, ipv6bytes, 0, IPV4_BYTES.length); + p1 = new IpPrefix(ipv6bytes, 32); + assertEqualBothWays(p1, new IpPrefix("c000:0204::/32")); + + p2 = new IpPrefix(IPV4_BYTES, 32); + assertNotEqualEitherWay(p1, p2); + } + + @Test + public void testContainsInetAddress() { + IpPrefix p = new IpPrefix("2001:db8:f00::ace:d00d/127"); + assertTrue(p.contains(address("2001:db8:f00::ace:d00c"))); + assertTrue(p.contains(address("2001:db8:f00::ace:d00d"))); + assertFalse(p.contains(address("2001:db8:f00::ace:d00e"))); + assertFalse(p.contains(address("2001:db8:f00::bad:d00d"))); + assertFalse(p.contains(address("2001:4868:4860::8888"))); + assertFalse(p.contains(address("8.8.8.8"))); + + p = new IpPrefix("192.0.2.0/23"); + assertTrue(p.contains(address("192.0.2.43"))); + assertTrue(p.contains(address("192.0.3.21"))); + assertFalse(p.contains(address("192.0.0.21"))); + assertFalse(p.contains(address("8.8.8.8"))); + assertFalse(p.contains(address("2001:4868:4860::8888"))); + + IpPrefix ipv6Default = new IpPrefix("::/0"); + assertTrue(ipv6Default.contains(address("2001:db8::f00"))); + assertFalse(ipv6Default.contains(address("192.0.2.1"))); + + IpPrefix ipv4Default = new IpPrefix("0.0.0.0/0"); + assertTrue(ipv4Default.contains(address("255.255.255.255"))); + assertTrue(ipv4Default.contains(address("192.0.2.1"))); + assertFalse(ipv4Default.contains(address("2001:db8::f00"))); + } + + @Test + public void testContainsIpPrefix() { + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("0.0.0.0/0"))); + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/0"))); + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/8"))); + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/24"))); + assertTrue(new IpPrefix("0.0.0.0/0").containsPrefix(new IpPrefix("1.2.3.4/23"))); + + assertTrue(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("1.2.3.4/8"))); + assertTrue(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("1.254.12.9/8"))); + assertTrue(new IpPrefix("1.2.3.4/21").containsPrefix(new IpPrefix("1.2.3.4/21"))); + assertTrue(new IpPrefix("1.2.3.4/32").containsPrefix(new IpPrefix("1.2.3.4/32"))); + + assertTrue(new IpPrefix("1.2.3.4/20").containsPrefix(new IpPrefix("1.2.3.0/24"))); + + assertFalse(new IpPrefix("1.2.3.4/32").containsPrefix(new IpPrefix("1.2.3.5/32"))); + assertFalse(new IpPrefix("1.2.3.4/8").containsPrefix(new IpPrefix("2.2.3.4/8"))); + assertFalse(new IpPrefix("0.0.0.0/16").containsPrefix(new IpPrefix("0.0.0.0/15"))); + assertFalse(new IpPrefix("100.0.0.0/8").containsPrefix(new IpPrefix("99.0.0.0/8"))); + + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("::/0"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/1"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("3d8a:661:a0::770/8"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/8"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/64"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/113"))); + assertTrue(new IpPrefix("::/0").containsPrefix(new IpPrefix("2001:db8::f00/128"))); + + assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00d/64"))); + assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00d/120"))); + assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00d/32"))); + assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/64").containsPrefix( + new IpPrefix("2006:db8:f00::ace:d00d/96"))); + + assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/128").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00d/128"))); + assertTrue(new IpPrefix("2001:db8:f00::ace:d00d/100").containsPrefix( + new IpPrefix("2001:db8:f00::ace:ccaf/110"))); + + assertFalse(new IpPrefix("2001:db8:f00::ace:d00d/128").containsPrefix( + new IpPrefix("2001:db8:f00::ace:d00e/128"))); + assertFalse(new IpPrefix("::/30").containsPrefix(new IpPrefix("::/29"))); + } + + @Test + public void testHashCode() { + IpPrefix p = new IpPrefix(new byte[4], 0); + Random random = new Random(); + for (int i = 0; i < 100; i++) { + final IpPrefix oldP = p; + if (random.nextBoolean()) { + // IPv4. + byte[] b = new byte[4]; + random.nextBytes(b); + p = new IpPrefix(b, random.nextInt(33)); + } else { + // IPv6. + byte[] b = new byte[16]; + random.nextBytes(b); + p = new IpPrefix(b, random.nextInt(129)); + } + if (p.equals(oldP)) { + assertEquals(p.hashCode(), oldP.hashCode()); + } + if (p.hashCode() != oldP.hashCode()) { + assertNotEquals(p, oldP); + } + } + } + + @Test + public void testHashCodeIsNotConstant() { + IpPrefix[] prefixes = { + new IpPrefix("2001:db8:f00::ace:d00d/127"), + new IpPrefix("192.0.2.0/23"), + new IpPrefix("::/0"), + new IpPrefix("0.0.0.0/0"), + }; + for (int i = 0; i < prefixes.length; i++) { + for (int j = i + 1; j < prefixes.length; j++) { + assertNotEquals(prefixes[i].hashCode(), prefixes[j].hashCode()); + } + } + } + + @Test + public void testMappedAddressesAreBroken() { + // 192.0.2.0/24 != ::ffff:c000:0204/120, but because we use InetAddress, + // we are unable to comprehend that. + byte[] ipv6bytes = { + (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff, + (byte) 192, (byte) 0, (byte) 2, (byte) 0}; + IpPrefix p = new IpPrefix(ipv6bytes, 120); + assertEquals(16, p.getRawAddress().length); // Fine. + assertArrayEquals(ipv6bytes, p.getRawAddress()); // Fine. + + // Broken. + assertEquals("192.0.2.0/120", p.toString()); + assertEquals(InetAddress.parseNumericAddress("192.0.2.0"), p.getAddress()); + } + + @Test + public void testParceling() { + IpPrefix p; + + p = new IpPrefix("2001:4860:db8::/64"); + assertParcelingIsLossless(p); + assertTrue(p.isIPv6()); + + p = new IpPrefix("192.0.2.0/25"); + assertParcelingIsLossless(p); + assertTrue(p.isIPv4()); + + assertFieldCountEquals(2, IpPrefix.class); + } +} diff --git a/tests/common/java/android/net/KeepalivePacketDataTest.kt b/tests/common/java/android/net/KeepalivePacketDataTest.kt new file mode 100644 index 0000000000..f464ec6cf0 --- /dev/null +++ b/tests/common/java/android/net/KeepalivePacketDataTest.kt @@ -0,0 +1,120 @@ +/* + * 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 android.net + +import android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS +import android.net.InvalidPacketException.ERROR_INVALID_PORT +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import java.net.InetAddress +import java.util.Arrays +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class KeepalivePacketDataTest { + @Rule @JvmField + val ignoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule() + + private val INVALID_PORT = 65537 + private val TEST_DST_PORT = 4244 + private val TEST_SRC_PORT = 4243 + + private val TESTBYTES = byteArrayOf(12, 31, 22, 44) + private val TEST_SRC_ADDRV4 = "198.168.0.2".address() + private val TEST_DST_ADDRV4 = "198.168.0.1".address() + private val TEST_ADDRV6 = "2001:db8::1".address() + + private fun String.address() = InetAddresses.parseNumericAddress(this) + + // Add for test because constructor of KeepalivePacketData is protected. + private inner class TestKeepalivePacketData( + srcAddress: InetAddress? = TEST_SRC_ADDRV4, + srcPort: Int = TEST_SRC_PORT, + dstAddress: InetAddress? = TEST_DST_ADDRV4, + dstPort: Int = TEST_DST_PORT, + data: ByteArray = TESTBYTES + ) : KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, data) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testConstructor() { + var data: TestKeepalivePacketData + + try { + data = TestKeepalivePacketData(srcAddress = null) + fail("Null src address should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + data = TestKeepalivePacketData(dstAddress = null) + fail("Null dst address should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + data = TestKeepalivePacketData(dstAddress = TEST_ADDRV6) + fail("Ip family mismatched should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + data = TestKeepalivePacketData(srcPort = INVALID_PORT) + fail("Invalid srcPort should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_PORT) + } + + try { + data = TestKeepalivePacketData(dstPort = INVALID_PORT) + fail("Invalid dstPort should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_PORT) + } + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testSrcAddress() = assertEquals(TEST_SRC_ADDRV4, TestKeepalivePacketData().srcAddress) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testDstAddress() = assertEquals(TEST_DST_ADDRV4, TestKeepalivePacketData().dstAddress) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testSrcPort() = assertEquals(TEST_SRC_PORT, TestKeepalivePacketData().srcPort) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testDstPort() = assertEquals(TEST_DST_PORT, TestKeepalivePacketData().dstPort) + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testPacket() = assertTrue(Arrays.equals(TESTBYTES, TestKeepalivePacketData().packet)) +} \ No newline at end of file diff --git a/tests/common/java/android/net/LinkAddressTest.java b/tests/common/java/android/net/LinkAddressTest.java new file mode 100644 index 0000000000..2cf3cf9c11 --- /dev/null +++ b/tests/common/java/android/net/LinkAddressTest.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2013 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; + +import static android.system.OsConstants.IFA_F_DADFAILED; +import static android.system.OsConstants.IFA_F_DEPRECATED; +import static android.system.OsConstants.IFA_F_OPTIMISTIC; +import static android.system.OsConstants.IFA_F_PERMANENT; +import static android.system.OsConstants.IFA_F_TEMPORARY; +import static android.system.OsConstants.IFA_F_TENTATIVE; +import static android.system.OsConstants.RT_SCOPE_HOST; +import static android.system.OsConstants.RT_SCOPE_LINK; +import static android.system.OsConstants.RT_SCOPE_SITE; +import static android.system.OsConstants.RT_SCOPE_UNIVERSE; + +import static com.android.testutils.MiscAsserts.assertEqualBothWays; +import static com.android.testutils.MiscAsserts.assertFieldCountEquals; +import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay; +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Build; +import android.os.SystemClock; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Arrays; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LinkAddressTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + + private static final String V4 = "192.0.2.1"; + private static final String V6 = "2001:db8::1"; + private static final InetAddress V4_ADDRESS = InetAddresses.parseNumericAddress(V4); + private static final InetAddress V6_ADDRESS = InetAddresses.parseNumericAddress(V6); + + @Test + public void testConstants() { + // RT_SCOPE_UNIVERSE = 0, but all the other constants should be nonzero. + assertNotEquals(0, RT_SCOPE_HOST); + assertNotEquals(0, RT_SCOPE_LINK); + assertNotEquals(0, RT_SCOPE_SITE); + + assertNotEquals(0, IFA_F_DEPRECATED); + assertNotEquals(0, IFA_F_PERMANENT); + assertNotEquals(0, IFA_F_TENTATIVE); + } + + @Test + public void testConstructors() throws SocketException { + LinkAddress address; + + // Valid addresses work as expected. + address = new LinkAddress(V4_ADDRESS, 25); + assertEquals(V4_ADDRESS, address.getAddress()); + assertEquals(25, address.getPrefixLength()); + assertEquals(0, address.getFlags()); + assertEquals(RT_SCOPE_UNIVERSE, address.getScope()); + assertTrue(address.isIpv4()); + + address = new LinkAddress(V6_ADDRESS, 127); + assertEquals(V6_ADDRESS, address.getAddress()); + assertEquals(127, address.getPrefixLength()); + assertEquals(0, address.getFlags()); + assertEquals(RT_SCOPE_UNIVERSE, address.getScope()); + assertTrue(address.isIpv6()); + + // Nonsensical flags/scopes or combinations thereof are acceptable. + address = new LinkAddress(V6 + "/64", IFA_F_DEPRECATED | IFA_F_PERMANENT, RT_SCOPE_LINK); + assertEquals(V6_ADDRESS, address.getAddress()); + assertEquals(64, address.getPrefixLength()); + assertEquals(IFA_F_DEPRECATED | IFA_F_PERMANENT, address.getFlags()); + assertEquals(RT_SCOPE_LINK, address.getScope()); + assertTrue(address.isIpv6()); + + address = new LinkAddress(V4 + "/23", 123, 456); + assertEquals(V4_ADDRESS, address.getAddress()); + assertEquals(23, address.getPrefixLength()); + assertEquals(123, address.getFlags()); + assertEquals(456, address.getScope()); + assertTrue(address.isIpv4()); + + address = new LinkAddress("/64", 1 /* flags */, 2 /* scope */); + assertEquals(Inet6Address.LOOPBACK, address.getAddress()); + assertEquals(64, address.getPrefixLength()); + assertEquals(1, address.getFlags()); + assertEquals(2, address.getScope()); + assertTrue(address.isIpv6()); + + address = new LinkAddress("[2001:db8::123]/64", 3 /* flags */, 4 /* scope */); + assertEquals(InetAddresses.parseNumericAddress("2001:db8::123"), address.getAddress()); + assertEquals(64, address.getPrefixLength()); + assertEquals(3, address.getFlags()); + assertEquals(4, address.getScope()); + assertTrue(address.isIpv6()); + + // InterfaceAddress doesn't have a constructor. Fetch some from an interface. + List addrs = NetworkInterface.getByName("lo").getInterfaceAddresses(); + + // We expect to find 127.0.0.1/8 and ::1/128, in any order. + LinkAddress ipv4Loopback, ipv6Loopback; + assertEquals(2, addrs.size()); + if (addrs.get(0).getAddress() instanceof Inet4Address) { + ipv4Loopback = new LinkAddress(addrs.get(0)); + ipv6Loopback = new LinkAddress(addrs.get(1)); + } else { + ipv4Loopback = new LinkAddress(addrs.get(1)); + ipv6Loopback = new LinkAddress(addrs.get(0)); + } + + assertEquals(InetAddresses.parseNumericAddress("127.0.0.1"), ipv4Loopback.getAddress()); + assertEquals(8, ipv4Loopback.getPrefixLength()); + + assertEquals(InetAddresses.parseNumericAddress("::1"), ipv6Loopback.getAddress()); + assertEquals(128, ipv6Loopback.getPrefixLength()); + + // Null addresses are rejected. + try { + address = new LinkAddress(null, 24); + fail("Null InetAddress should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + try { + address = new LinkAddress((String) null, IFA_F_PERMANENT, RT_SCOPE_UNIVERSE); + fail("Null string should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + try { + address = new LinkAddress((InterfaceAddress) null); + fail("Null string should cause NullPointerException"); + } catch(NullPointerException expected) {} + + // Invalid prefix lengths are rejected. + try { + address = new LinkAddress(V4_ADDRESS, -1); + fail("Negative IPv4 prefix length should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + try { + address = new LinkAddress(V6_ADDRESS, -1); + fail("Negative IPv6 prefix length should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + try { + address = new LinkAddress(V4_ADDRESS, 33); + fail("/33 IPv4 prefix length should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + try { + address = new LinkAddress(V4 + "/33", IFA_F_PERMANENT, RT_SCOPE_UNIVERSE); + fail("/33 IPv4 prefix length should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + + try { + address = new LinkAddress(V6_ADDRESS, 129, IFA_F_PERMANENT, RT_SCOPE_UNIVERSE); + fail("/129 IPv6 prefix length should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + try { + address = new LinkAddress(V6 + "/129", IFA_F_PERMANENT, RT_SCOPE_UNIVERSE); + fail("/129 IPv6 prefix length should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + // Multicast addresses are rejected. + try { + address = new LinkAddress("224.0.0.2/32"); + fail("IPv4 multicast address should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + + try { + address = new LinkAddress("ff02::1/128"); + fail("IPv6 multicast address should cause IllegalArgumentException"); + } catch(IllegalArgumentException expected) {} + } + + @Test + public void testAddressScopes() { + assertEquals(RT_SCOPE_HOST, new LinkAddress("::/128").getScope()); + assertEquals(RT_SCOPE_HOST, new LinkAddress("0.0.0.0/32").getScope()); + + assertEquals(RT_SCOPE_LINK, new LinkAddress("::1/128").getScope()); + assertEquals(RT_SCOPE_LINK, new LinkAddress("127.0.0.5/8").getScope()); + assertEquals(RT_SCOPE_LINK, new LinkAddress("fe80::ace:d00d/64").getScope()); + assertEquals(RT_SCOPE_LINK, new LinkAddress("169.254.5.12/16").getScope()); + + assertEquals(RT_SCOPE_SITE, new LinkAddress("fec0::dead/64").getScope()); + + assertEquals(RT_SCOPE_UNIVERSE, new LinkAddress("10.1.2.3/21").getScope()); + assertEquals(RT_SCOPE_UNIVERSE, new LinkAddress("192.0.2.1/25").getScope()); + assertEquals(RT_SCOPE_UNIVERSE, new LinkAddress("2001:db8::/64").getScope()); + assertEquals(RT_SCOPE_UNIVERSE, new LinkAddress("5000::/127").getScope()); + } + + private void assertIsSameAddressAs(LinkAddress l1, LinkAddress l2) { + assertTrue(l1 + " unexpectedly does not have same address as " + l2, + l1.isSameAddressAs(l2)); + assertTrue(l2 + " unexpectedly does not have same address as " + l1, + l2.isSameAddressAs(l1)); + } + + private void assertIsNotSameAddressAs(LinkAddress l1, LinkAddress l2) { + assertFalse(l1 + " unexpectedly has same address as " + l2, + l1.isSameAddressAs(l2)); + assertFalse(l2 + " unexpectedly has same address as " + l1, + l1.isSameAddressAs(l2)); + } + + @Test + public void testEqualsAndSameAddressAs() { + LinkAddress l1, l2, l3; + + l1 = new LinkAddress("2001:db8::1/64"); + l2 = new LinkAddress("2001:db8::1/64"); + assertEqualBothWays(l1, l2); + assertIsSameAddressAs(l1, l2); + + l2 = new LinkAddress("2001:db8::1/65"); + assertNotEqualEitherWay(l1, l2); + assertIsNotSameAddressAs(l1, l2); + + l2 = new LinkAddress("2001:db8::2/64"); + assertNotEqualEitherWay(l1, l2); + assertIsNotSameAddressAs(l1, l2); + + + l1 = new LinkAddress("192.0.2.1/24"); + l2 = new LinkAddress("192.0.2.1/24"); + assertEqualBothWays(l1, l2); + assertIsSameAddressAs(l1, l2); + + l2 = new LinkAddress("192.0.2.1/23"); + assertNotEqualEitherWay(l1, l2); + assertIsNotSameAddressAs(l1, l2); + + l2 = new LinkAddress("192.0.2.2/24"); + assertNotEqualEitherWay(l1, l2); + assertIsNotSameAddressAs(l1, l2); + + + // Check equals() and isSameAddressAs() on identical addresses with different flags. + l1 = new LinkAddress(V6_ADDRESS, 64); + l2 = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_UNIVERSE); + assertEqualBothWays(l1, l2); + assertIsSameAddressAs(l1, l2); + + l2 = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_UNIVERSE); + assertNotEqualEitherWay(l1, l2); + assertIsSameAddressAs(l1, l2); + + // Check equals() and isSameAddressAs() on identical addresses with different scope. + l1 = new LinkAddress(V4_ADDRESS, 24); + l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_UNIVERSE); + assertEqualBothWays(l1, l2); + assertIsSameAddressAs(l1, l2); + + l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_HOST); + assertNotEqualEitherWay(l1, l2); + assertIsSameAddressAs(l1, l2); + + // Addresses with the same start or end bytes aren't equal between families. + l1 = new LinkAddress("32.1.13.184/24"); + l2 = new LinkAddress("2001:db8::1/24"); + l3 = new LinkAddress("::2001:db8/24"); + + byte[] ipv4Bytes = l1.getAddress().getAddress(); + byte[] l2FirstIPv6Bytes = Arrays.copyOf(l2.getAddress().getAddress(), 4); + byte[] l3LastIPv6Bytes = Arrays.copyOfRange(l3.getAddress().getAddress(), 12, 16); + assertTrue(Arrays.equals(ipv4Bytes, l2FirstIPv6Bytes)); + assertTrue(Arrays.equals(ipv4Bytes, l3LastIPv6Bytes)); + + assertNotEqualEitherWay(l1, l2); + assertIsNotSameAddressAs(l1, l2); + + assertNotEqualEitherWay(l1, l3); + assertIsNotSameAddressAs(l1, l3); + + // Because we use InetAddress, an IPv4 address is equal to its IPv4-mapped address. + // TODO: Investigate fixing this. + String addressString = V4 + "/24"; + l1 = new LinkAddress(addressString); + l2 = new LinkAddress("::ffff:" + addressString); + assertEqualBothWays(l1, l2); + assertIsSameAddressAs(l1, l2); + } + + @Test + public void testHashCode() { + LinkAddress l1, l2; + + l1 = new LinkAddress(V4_ADDRESS, 23); + l2 = new LinkAddress(V4_ADDRESS, 23, 0, RT_SCOPE_HOST); + assertNotEquals(l1.hashCode(), l2.hashCode()); + + l1 = new LinkAddress(V6_ADDRESS, 128); + l2 = new LinkAddress(V6_ADDRESS, 128, IFA_F_TENTATIVE, RT_SCOPE_UNIVERSE); + assertNotEquals(l1.hashCode(), l2.hashCode()); + } + + @Test + public void testParceling() { + LinkAddress l; + + l = new LinkAddress(V6_ADDRESS, 64, 123, 456); + assertParcelingIsLossless(l); + + l = new LinkAddress(V4 + "/28", IFA_F_PERMANENT, RT_SCOPE_LINK); + assertParcelingIsLossless(l); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testLifetimeParceling() { + final LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 123, 456, 1L, 3600000L); + assertParcelingIsLossless(l); + } + + @Test @IgnoreAfter(Build.VERSION_CODES.Q) + public void testFieldCount_Q() { + assertFieldCountEquals(4, LinkAddress.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testFieldCount() { + // Make sure any new field is covered by the above parceling tests when changing this number + assertFieldCountEquals(6, LinkAddress.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testDeprecationTime() { + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + LinkAddress.LIFETIME_UNKNOWN, 100000L); + fail("Only one time provided should cause exception"); + } catch (IllegalArgumentException expected) { } + + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + 200000L, 100000L); + fail("deprecation time later than expiration time should cause exception"); + } catch (IllegalArgumentException expected) { } + + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + -2, 100000L); + fail("negative deprecation time should cause exception"); + } catch (IllegalArgumentException expected) { } + + LinkAddress addr = new LinkAddress(V6_ADDRESS, 64, 0, 456, 100000L, 200000L); + assertEquals(100000L, addr.getDeprecationTime()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testExpirationTime() { + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + 200000L, LinkAddress.LIFETIME_UNKNOWN); + fail("Only one time provided should cause exception"); + } catch (IllegalArgumentException expected) { } + + try { + new LinkAddress(V6_ADDRESS, 64, 0, 456, + 100000L, -2); + fail("negative expiration time should cause exception"); + } catch (IllegalArgumentException expected) { } + + LinkAddress addr = new LinkAddress(V6_ADDRESS, 64, 0, 456, 100000L, 200000L); + assertEquals(200000L, addr.getExpirationTime()); + } + + @Test + public void testGetFlags() { + LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 123, RT_SCOPE_HOST); + assertEquals(123, l.getFlags()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testGetFlags_Deprecation() { + // Test if deprecated bit was added/remove automatically based on the provided deprecation + // time + LinkAddress l = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_HOST, + 1L, LinkAddress.LIFETIME_PERMANENT); + // Check if the flag is added automatically. + assertTrue((l.getFlags() & IFA_F_DEPRECATED) != 0); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_HOST, + SystemClock.elapsedRealtime() + 100000L, LinkAddress.LIFETIME_PERMANENT); + // Check if the flag is removed automatically. + assertTrue((l.getFlags() & IFA_F_DEPRECATED) == 0); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_HOST, + LinkAddress.LIFETIME_PERMANENT, LinkAddress.LIFETIME_PERMANENT); + // Check if the permanent flag is added. + assertTrue((l.getFlags() & IFA_F_PERMANENT) != 0); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_PERMANENT, RT_SCOPE_HOST, + 1000L, SystemClock.elapsedRealtime() + 100000L); + // Check if the permanent flag is removed + assertTrue((l.getFlags() & IFA_F_PERMANENT) == 0); + } + + private void assertGlobalPreferred(LinkAddress l, String msg) { + assertTrue(msg, l.isGlobalPreferred()); + } + + private void assertNotGlobalPreferred(LinkAddress l, String msg) { + assertFalse(msg, l.isGlobalPreferred()); + } + + @Test + public void testIsGlobalPreferred() { + LinkAddress l; + + l = new LinkAddress(V4_ADDRESS, 32, 0, RT_SCOPE_UNIVERSE); + assertGlobalPreferred(l, "v4,global,noflags"); + + l = new LinkAddress("10.10.1.7/23", 0, RT_SCOPE_UNIVERSE); + assertGlobalPreferred(l, "v4-rfc1918,global,noflags"); + + l = new LinkAddress("10.10.1.7/23", 0, RT_SCOPE_SITE); + assertNotGlobalPreferred(l, "v4-rfc1918,site-local,noflags"); + + l = new LinkAddress("127.0.0.7/8", 0, RT_SCOPE_HOST); + assertNotGlobalPreferred(l, "v4-localhost,node-local,noflags"); + + l = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_UNIVERSE); + assertGlobalPreferred(l, "v6,global,noflags"); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_PERMANENT, RT_SCOPE_UNIVERSE); + assertGlobalPreferred(l, "v6,global,permanent"); + + // IPv6 ULAs are not acceptable "global preferred" addresses. + l = new LinkAddress("fc12::1/64", 0, RT_SCOPE_UNIVERSE); + assertNotGlobalPreferred(l, "v6,ula1,noflags"); + + l = new LinkAddress("fd34::1/64", 0, RT_SCOPE_UNIVERSE); + assertNotGlobalPreferred(l, "v6,ula2,noflags"); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_TEMPORARY, RT_SCOPE_UNIVERSE); + assertGlobalPreferred(l, "v6,global,tempaddr"); + + l = new LinkAddress(V6_ADDRESS, 64, (IFA_F_TEMPORARY|IFA_F_DADFAILED), + RT_SCOPE_UNIVERSE); + assertNotGlobalPreferred(l, "v6,global,tempaddr+dadfailed"); + + l = new LinkAddress(V6_ADDRESS, 64, (IFA_F_TEMPORARY|IFA_F_DEPRECATED), + RT_SCOPE_UNIVERSE); + assertNotGlobalPreferred(l, "v6,global,tempaddr+deprecated"); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_TEMPORARY, RT_SCOPE_SITE); + assertNotGlobalPreferred(l, "v6,site-local,tempaddr"); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_TEMPORARY, RT_SCOPE_LINK); + assertNotGlobalPreferred(l, "v6,link-local,tempaddr"); + + l = new LinkAddress(V6_ADDRESS, 64, IFA_F_TEMPORARY, RT_SCOPE_HOST); + assertNotGlobalPreferred(l, "v6,node-local,tempaddr"); + + l = new LinkAddress("::1/128", IFA_F_PERMANENT, RT_SCOPE_HOST); + assertNotGlobalPreferred(l, "v6-localhost,node-local,permanent"); + + l = new LinkAddress(V6_ADDRESS, 64, (IFA_F_TEMPORARY|IFA_F_TENTATIVE), + RT_SCOPE_UNIVERSE); + assertNotGlobalPreferred(l, "v6,global,tempaddr+tentative"); + + l = new LinkAddress(V6_ADDRESS, 64, + (IFA_F_TEMPORARY|IFA_F_TENTATIVE|IFA_F_OPTIMISTIC), + RT_SCOPE_UNIVERSE); + assertGlobalPreferred(l, "v6,global,tempaddr+optimistic"); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testIsGlobalPreferred_DeprecatedInFuture() { + final LinkAddress l = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, + RT_SCOPE_UNIVERSE, SystemClock.elapsedRealtime() + 100000, + SystemClock.elapsedRealtime() + 200000); + // Although the deprecated bit is set, but the deprecation time is in the future, test + // if the flag is removed automatically. + assertGlobalPreferred(l, "v6,global,tempaddr+deprecated in the future"); + } +} diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java new file mode 100644 index 0000000000..550953d061 --- /dev/null +++ b/tests/common/java/android/net/LinkPropertiesTest.java @@ -0,0 +1,1271 @@ +/* + * Copyright (C) 2010 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; + +import static android.net.RouteInfo.RTN_THROW; +import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.RouteInfo.RTN_UNREACHABLE; + +import static com.android.testutils.ParcelUtils.assertParcelSane; +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; +import static com.android.testutils.ParcelUtils.parcelingRoundTrip; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.LinkProperties.ProvisioningChange; +import android.os.Build; +import android.system.OsConstants; +import android.util.ArraySet; + +import androidx.core.os.BuildCompat; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.LinkPropertiesUtils.CompareResult; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LinkPropertiesTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + + private static final InetAddress ADDRV4 = address("75.208.6.1"); + private static final InetAddress ADDRV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + private static final InetAddress DNS1 = address("75.208.7.1"); + private static final InetAddress DNS2 = address("69.78.7.1"); + private static final InetAddress DNS6 = address("2001:4860:4860::8888"); + private static final InetAddress PRIVDNS1 = address("1.1.1.1"); + private static final InetAddress PRIVDNS2 = address("1.0.0.1"); + private static final InetAddress PRIVDNS6 = address("2606:4700:4700::1111"); + private static final InetAddress PCSCFV4 = address("10.77.25.37"); + private static final InetAddress PCSCFV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:1"); + private static final InetAddress GATEWAY1 = address("75.208.8.1"); + private static final InetAddress GATEWAY2 = address("69.78.8.1"); + private static final InetAddress GATEWAY61 = address("fe80::6:0000:613"); + private static final InetAddress GATEWAY62 = address("fe80::6:22%lo"); + private static final InetAddress TESTIPV4ADDR = address("192.168.47.42"); + private static final InetAddress TESTIPV6ADDR = address("fe80::7:33%43"); + private static final Inet4Address DHCPSERVER = (Inet4Address) address("192.0.2.1"); + private static final String NAME = "qmi0"; + private static final String DOMAINS = "google.com"; + private static final String PRIV_DNS_SERVER_NAME = "private.dns.com"; + private static final String TCP_BUFFER_SIZES = "524288,1048576,2097152,262144,524288,1048576"; + private static final int MTU = 1500; + private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32); + private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128); + private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64"); + private static final Uri CAPPORT_API_URL = Uri.parse("https://test.example.com/capportapi"); + + // CaptivePortalData cannot be in a constant as it does not exist on Q. + // The test runner also crashes when scanning for tests if it is a return type. + private static Object getCaptivePortalData() { + return new CaptivePortalData.Builder() + .setVenueInfoUrl(Uri.parse("https://test.example.com/venue")).build(); + } + + private static InetAddress address(String addrString) { + return InetAddresses.parseNumericAddress(addrString); + } + + private static boolean isAtLeastR() { + // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R) + return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR(); + } + + private void checkEmpty(final LinkProperties lp) { + assertEquals(0, lp.getAllInterfaceNames().size()); + assertEquals(0, lp.getAllAddresses().size()); + assertEquals(0, lp.getDnsServers().size()); + assertEquals(0, lp.getValidatedPrivateDnsServers().size()); + assertEquals(0, lp.getPcscfServers().size()); + assertEquals(0, lp.getAllRoutes().size()); + assertEquals(0, lp.getAllLinkAddresses().size()); + assertEquals(0, lp.getStackedLinks().size()); + assertEquals(0, lp.getMtu()); + assertNull(lp.getPrivateDnsServerName()); + assertNull(lp.getDomains()); + assertNull(lp.getHttpProxy()); + assertNull(lp.getTcpBufferSizes()); + assertNull(lp.getNat64Prefix()); + assertFalse(lp.isProvisioned()); + assertFalse(lp.isIpv4Provisioned()); + assertFalse(lp.isIpv6Provisioned()); + assertFalse(lp.isPrivateDnsActive()); + + if (isAtLeastR()) { + assertNull(lp.getDhcpServerAddress()); + assertFalse(lp.isWakeOnLanSupported()); + assertNull(lp.getCaptivePortalApiUrl()); + assertNull(lp.getCaptivePortalData()); + } + } + + private LinkProperties makeTestObject() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(NAME); + lp.addLinkAddress(LINKADDRV4); + lp.addLinkAddress(LINKADDRV6); + lp.addDnsServer(DNS1); + lp.addDnsServer(DNS2); + lp.addValidatedPrivateDnsServer(PRIVDNS1); + lp.addValidatedPrivateDnsServer(PRIVDNS2); + lp.setUsePrivateDns(true); + lp.setPrivateDnsServerName(PRIV_DNS_SERVER_NAME); + lp.addPcscfServer(PCSCFV6); + lp.setDomains(DOMAINS); + lp.addRoute(new RouteInfo(GATEWAY1)); + lp.addRoute(new RouteInfo(GATEWAY2)); + lp.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888)); + lp.setMtu(MTU); + lp.setTcpBufferSizes(TCP_BUFFER_SIZES); + lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96")); + if (isAtLeastR()) { + lp.setDhcpServerAddress(DHCPSERVER); + lp.setWakeOnLanSupported(true); + lp.setCaptivePortalApiUrl(CAPPORT_API_URL); + lp.setCaptivePortalData((CaptivePortalData) getCaptivePortalData()); + } + return lp; + } + + public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) { + // Check implementation of equals(), element by element. + assertTrue(source.isIdenticalInterfaceName(target)); + assertTrue(target.isIdenticalInterfaceName(source)); + + assertTrue(source.isIdenticalAddresses(target)); + assertTrue(target.isIdenticalAddresses(source)); + + assertTrue(source.isIdenticalDnses(target)); + assertTrue(target.isIdenticalDnses(source)); + + assertTrue(source.isIdenticalPrivateDns(target)); + assertTrue(target.isIdenticalPrivateDns(source)); + + assertTrue(source.isIdenticalValidatedPrivateDnses(target)); + assertTrue(target.isIdenticalValidatedPrivateDnses(source)); + + assertTrue(source.isIdenticalPcscfs(target)); + assertTrue(target.isIdenticalPcscfs(source)); + + assertTrue(source.isIdenticalRoutes(target)); + assertTrue(target.isIdenticalRoutes(source)); + + assertTrue(source.isIdenticalHttpProxy(target)); + assertTrue(target.isIdenticalHttpProxy(source)); + + assertTrue(source.isIdenticalStackedLinks(target)); + assertTrue(target.isIdenticalStackedLinks(source)); + + assertTrue(source.isIdenticalMtu(target)); + assertTrue(target.isIdenticalMtu(source)); + + assertTrue(source.isIdenticalTcpBufferSizes(target)); + assertTrue(target.isIdenticalTcpBufferSizes(source)); + + if (isAtLeastR()) { + assertTrue(source.isIdenticalDhcpServerAddress(target)); + assertTrue(source.isIdenticalDhcpServerAddress(source)); + + assertTrue(source.isIdenticalWakeOnLan(target)); + assertTrue(target.isIdenticalWakeOnLan(source)); + + assertTrue(source.isIdenticalCaptivePortalApiUrl(target)); + assertTrue(target.isIdenticalCaptivePortalApiUrl(source)); + + assertTrue(source.isIdenticalCaptivePortalData(target)); + assertTrue(target.isIdenticalCaptivePortalData(source)); + } + + // Check result of equals(). + assertTrue(source.equals(target)); + assertTrue(target.equals(source)); + + // Check hashCode. + assertEquals(source.hashCode(), target.hashCode()); + } + + @Test + public void testEqualsNull() { + LinkProperties source = new LinkProperties(); + LinkProperties target = new LinkProperties(); + + assertFalse(source == target); + assertLinkPropertiesEqual(source, target); + } + + @Test + public void testEqualsSameOrder() throws Exception { + LinkProperties source = new LinkProperties(); + source.setInterfaceName(NAME); + // set 2 link addresses + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV6); + // set 2 dnses + source.addDnsServer(DNS1); + source.addDnsServer(DNS2); + // set 1 pcscf + source.addPcscfServer(PCSCFV6); + // set 2 gateways + source.addRoute(new RouteInfo(GATEWAY1)); + source.addRoute(new RouteInfo(GATEWAY2)); + source.setMtu(MTU); + + LinkProperties target = new LinkProperties(); + + // All fields are same + target.setInterfaceName(NAME); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDnsServer(DNS1); + target.addDnsServer(DNS2); + target.addPcscfServer(PCSCFV6); + target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(GATEWAY2)); + target.setMtu(MTU); + + assertLinkPropertiesEqual(source, target); + + target.clear(); + // change Interface Name + target.setInterfaceName("qmi1"); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDnsServer(DNS1); + target.addDnsServer(DNS2); + target.addPcscfServer(PCSCFV6); + target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(GATEWAY2)); + target.setMtu(MTU); + assertFalse(source.equals(target)); + + target.clear(); + target.setInterfaceName(NAME); + // change link addresses + target.addLinkAddress(new LinkAddress(address("75.208.6.2"), 32)); + target.addLinkAddress(LINKADDRV6); + target.addDnsServer(DNS1); + target.addDnsServer(DNS2); + target.addPcscfServer(PCSCFV6); + target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(GATEWAY2)); + target.setMtu(MTU); + assertFalse(source.equals(target)); + + target.clear(); + target.setInterfaceName(NAME); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + // change dnses + target.addDnsServer(address("75.208.7.2")); + target.addDnsServer(DNS2); + target.addPcscfServer(PCSCFV6); + target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(GATEWAY2)); + target.setMtu(MTU); + assertFalse(source.equals(target)); + + target.clear(); + target.setInterfaceName(NAME); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDnsServer(address("75.208.7.2")); + target.addDnsServer(DNS2); + // change pcscf + target.addPcscfServer(address("2001::1")); + target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(GATEWAY2)); + target.setMtu(MTU); + assertFalse(source.equals(target)); + + target.clear(); + target.setInterfaceName(NAME); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDnsServer(DNS1); + target.addDnsServer(DNS2); + // change gateway + target.addRoute(new RouteInfo(address("75.208.8.2"))); + target.setMtu(MTU); + target.addRoute(new RouteInfo(GATEWAY2)); + assertFalse(source.equals(target)); + + target.clear(); + target.setInterfaceName(NAME); + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addDnsServer(DNS1); + target.addDnsServer(DNS2); + target.addRoute(new RouteInfo(GATEWAY1)); + target.addRoute(new RouteInfo(GATEWAY2)); + // change mtu + target.setMtu(1440); + assertFalse(source.equals(target)); + } + + @Test + public void testEqualsDifferentOrder() throws Exception { + LinkProperties source = new LinkProperties(); + source.setInterfaceName(NAME); + // set 2 link addresses + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV6); + // set 2 dnses + source.addDnsServer(DNS1); + source.addDnsServer(DNS2); + // set 2 gateways + source.addRoute(new RouteInfo(LINKADDRV4, GATEWAY1)); + source.addRoute(new RouteInfo(GATEWAY2)); + source.setMtu(MTU); + + LinkProperties target = new LinkProperties(); + // Exchange order + target.setInterfaceName(NAME); + target.addLinkAddress(LINKADDRV6); + target.addLinkAddress(LINKADDRV4); + target.addDnsServer(DNS2); + target.addDnsServer(DNS1); + target.addRoute(new RouteInfo(GATEWAY2)); + target.addRoute(new RouteInfo(LINKADDRV4, GATEWAY1)); + target.setMtu(MTU); + + assertLinkPropertiesEqual(source, target); + } + + @Test + public void testEqualsDuplicated() throws Exception { + LinkProperties source = new LinkProperties(); + // set 3 link addresses, eg, [A, A, B] + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV6); + + LinkProperties target = new LinkProperties(); + // set 3 link addresses, eg, [A, B, B] + target.addLinkAddress(LINKADDRV4); + target.addLinkAddress(LINKADDRV6); + target.addLinkAddress(LINKADDRV6); + + assertLinkPropertiesEqual(source, target); + } + + private void assertAllRoutesHaveInterface(String iface, LinkProperties lp) { + for (RouteInfo r : lp.getRoutes()) { + assertEquals(iface, r.getInterface()); + } + } + + private void assertAllRoutesNotHaveInterface(String iface, LinkProperties lp) { + for (RouteInfo r : lp.getRoutes()) { + assertNotEquals(iface, r.getInterface()); + } + } + + @Test + public void testRouteInterfaces() { + LinkAddress prefix1 = new LinkAddress(address("2001:db8:1::"), 48); + LinkAddress prefix2 = new LinkAddress(address("2001:db8:2::"), 48); + InetAddress address = ADDRV6; + + // Add a route with no interface to a LinkProperties with no interface. No errors. + LinkProperties lp = new LinkProperties(); + RouteInfo r = new RouteInfo(prefix1, address, null); + assertTrue(lp.addRoute(r)); + assertEquals(1, lp.getRoutes().size()); + assertAllRoutesHaveInterface(null, lp); + + // Adding the same route twice has no effect. + assertFalse(lp.addRoute(r)); + assertEquals(1, lp.getRoutes().size()); + + // Add a route with an interface. Expect an exception. + r = new RouteInfo(prefix2, address, "wlan0"); + try { + lp.addRoute(r); + fail("Adding wlan0 route to LP with no interface, expect exception"); + } catch (IllegalArgumentException expected) {} + + // Change the interface name. All the routes should change their interface name too. + lp.setInterfaceName("rmnet0"); + assertAllRoutesHaveInterface("rmnet0", lp); + assertAllRoutesNotHaveInterface(null, lp); + assertAllRoutesNotHaveInterface("wlan0", lp); + + // Now add a route with the wrong interface. This causes an exception too. + try { + lp.addRoute(r); + fail("Adding wlan0 route to rmnet0 LP, expect exception"); + } catch (IllegalArgumentException expected) {} + + // If the interface name matches, the route is added. + r = new RouteInfo(prefix2, null, "wlan0"); + lp.setInterfaceName("wlan0"); + lp.addRoute(r); + assertEquals(2, lp.getRoutes().size()); + assertAllRoutesHaveInterface("wlan0", lp); + assertAllRoutesNotHaveInterface("rmnet0", lp); + + // Routes with null interfaces are converted to wlan0. + r = RouteInfo.makeHostRoute(ADDRV6, null); + lp.addRoute(r); + assertEquals(3, lp.getRoutes().size()); + assertAllRoutesHaveInterface("wlan0", lp); + + // Check routes are updated correctly when calling setInterfaceName. + LinkProperties lp2 = new LinkProperties(lp); + assertAllRoutesHaveInterface("wlan0", lp2); + final CompareResult cr1 = + new CompareResult<>(lp.getAllRoutes(), lp2.getAllRoutes()); + assertEquals(0, cr1.added.size()); + assertEquals(0, cr1.removed.size()); + + lp2.setInterfaceName("p2p0"); + assertAllRoutesHaveInterface("p2p0", lp2); + assertAllRoutesNotHaveInterface("wlan0", lp2); + final CompareResult cr2 = + new CompareResult<>(lp.getAllRoutes(), lp2.getAllRoutes()); + assertEquals(3, cr2.added.size()); + assertEquals(3, cr2.removed.size()); + + // Remove route with incorrect interface, no route removed. + lp.removeRoute(new RouteInfo(prefix2, null, null)); + assertEquals(3, lp.getRoutes().size()); + + // Check remove works when interface is correct. + lp.removeRoute(new RouteInfo(prefix2, null, "wlan0")); + assertEquals(2, lp.getRoutes().size()); + assertAllRoutesHaveInterface("wlan0", lp); + assertAllRoutesNotHaveInterface("p2p0", lp); + } + + @Test + public void testStackedInterfaces() { + LinkProperties rmnet0 = new LinkProperties(); + rmnet0.setInterfaceName("rmnet0"); + rmnet0.addLinkAddress(LINKADDRV6); + + LinkProperties clat4 = new LinkProperties(); + clat4.setInterfaceName("clat4"); + clat4.addLinkAddress(LINKADDRV4); + + assertEquals(0, rmnet0.getStackedLinks().size()); + assertEquals(1, rmnet0.getAddresses().size()); + assertEquals(1, rmnet0.getLinkAddresses().size()); + assertEquals(1, rmnet0.getAllAddresses().size()); + assertEquals(1, rmnet0.getAllLinkAddresses().size()); + assertEquals(1, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); + + rmnet0.addStackedLink(clat4); + assertEquals(1, rmnet0.getStackedLinks().size()); + assertEquals(1, rmnet0.getAddresses().size()); + assertEquals(1, rmnet0.getLinkAddresses().size()); + assertEquals(2, rmnet0.getAllAddresses().size()); + assertEquals(2, rmnet0.getAllLinkAddresses().size()); + assertEquals(2, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); + assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1)); + + rmnet0.addStackedLink(clat4); + assertEquals(1, rmnet0.getStackedLinks().size()); + assertEquals(1, rmnet0.getAddresses().size()); + assertEquals(1, rmnet0.getLinkAddresses().size()); + assertEquals(2, rmnet0.getAllAddresses().size()); + assertEquals(2, rmnet0.getAllLinkAddresses().size()); + assertEquals(2, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); + assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1)); + + assertEquals(0, clat4.getStackedLinks().size()); + + // Modify an item in the returned collection to see what happens. + for (LinkProperties link : rmnet0.getStackedLinks()) { + if (link.getInterfaceName().equals("clat4")) { + link.setInterfaceName("newname"); + } + } + for (LinkProperties link : rmnet0.getStackedLinks()) { + assertFalse("newname".equals(link.getInterfaceName())); + } + + assertTrue(rmnet0.removeStackedLink("clat4")); + assertEquals(0, rmnet0.getStackedLinks().size()); + assertEquals(1, rmnet0.getAddresses().size()); + assertEquals(1, rmnet0.getLinkAddresses().size()); + assertEquals(1, rmnet0.getAllAddresses().size()); + assertEquals(1, rmnet0.getAllLinkAddresses().size()); + assertEquals(1, rmnet0.getAllInterfaceNames().size()); + assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0)); + + assertFalse(rmnet0.removeStackedLink("clat4")); + } + + private LinkAddress getFirstLinkAddress(LinkProperties lp) { + return lp.getLinkAddresses().iterator().next(); + } + + @Test + public void testAddressMethods() { + LinkProperties lp = new LinkProperties(); + + // No addresses. + assertFalse(lp.hasIpv4Address()); + assertFalse(lp.hasGlobalIpv6Address()); + + // Addresses on stacked links don't count. + LinkProperties stacked = new LinkProperties(); + stacked.setInterfaceName("stacked"); + lp.addStackedLink(stacked); + stacked.addLinkAddress(LINKADDRV4); + stacked.addLinkAddress(LINKADDRV6); + assertTrue(stacked.hasIpv4Address()); + assertTrue(stacked.hasGlobalIpv6Address()); + assertFalse(lp.hasIpv4Address()); + assertFalse(lp.hasGlobalIpv6Address()); + lp.removeStackedLink("stacked"); + assertFalse(lp.hasIpv4Address()); + assertFalse(lp.hasGlobalIpv6Address()); + + // Addresses on the base link. + // Check the return values of hasIpvXAddress and ensure the add/remove methods return true + // iff something changes. + assertEquals(0, lp.getLinkAddresses().size()); + assertTrue(lp.addLinkAddress(LINKADDRV6)); + assertEquals(1, lp.getLinkAddresses().size()); + assertFalse(lp.hasIpv4Address()); + assertTrue(lp.hasGlobalIpv6Address()); + + assertTrue(lp.removeLinkAddress(LINKADDRV6)); + assertEquals(0, lp.getLinkAddresses().size()); + + assertTrue(lp.addLinkAddress(LINKADDRV6LINKLOCAL)); + assertEquals(1, lp.getLinkAddresses().size()); + assertFalse(lp.hasGlobalIpv6Address()); + + assertTrue(lp.addLinkAddress(LINKADDRV4)); + assertEquals(2, lp.getLinkAddresses().size()); + assertTrue(lp.hasIpv4Address()); + assertFalse(lp.hasGlobalIpv6Address()); + + assertTrue(lp.addLinkAddress(LINKADDRV6)); + assertEquals(3, lp.getLinkAddresses().size()); + assertTrue(lp.hasIpv4Address()); + assertTrue(lp.hasGlobalIpv6Address()); + + assertTrue(lp.removeLinkAddress(LINKADDRV6LINKLOCAL)); + assertEquals(2, lp.getLinkAddresses().size()); + assertTrue(lp.hasIpv4Address()); + assertTrue(lp.hasGlobalIpv6Address()); + + // Adding an address twice has no effect. + // Removing an address that's not present has no effect. + assertFalse(lp.addLinkAddress(LINKADDRV4)); + assertEquals(2, lp.getLinkAddresses().size()); + assertTrue(lp.hasIpv4Address()); + assertTrue(lp.removeLinkAddress(LINKADDRV4)); + assertEquals(1, lp.getLinkAddresses().size()); + assertFalse(lp.hasIpv4Address()); + assertFalse(lp.removeLinkAddress(LINKADDRV4)); + assertEquals(1, lp.getLinkAddresses().size()); + + // Adding an address that's already present but with different properties causes the + // existing address to be updated and returns true. + // Start with only LINKADDRV6. + assertEquals(1, lp.getLinkAddresses().size()); + assertEquals(LINKADDRV6, getFirstLinkAddress(lp)); + + // Create a LinkAddress object for the same address, but with different flags. + LinkAddress deprecated = new LinkAddress(ADDRV6, 128, + OsConstants.IFA_F_DEPRECATED, OsConstants.RT_SCOPE_UNIVERSE); + assertTrue(deprecated.isSameAddressAs(LINKADDRV6)); + assertFalse(deprecated.equals(LINKADDRV6)); + + // Check that adding it updates the existing address instead of adding a new one. + assertTrue(lp.addLinkAddress(deprecated)); + assertEquals(1, lp.getLinkAddresses().size()); + assertEquals(deprecated, getFirstLinkAddress(lp)); + assertFalse(LINKADDRV6.equals(getFirstLinkAddress(lp))); + + // Removing LINKADDRV6 removes deprecated, because removing addresses ignores properties. + assertTrue(lp.removeLinkAddress(LINKADDRV6)); + assertEquals(0, lp.getLinkAddresses().size()); + } + + @Test + public void testLinkAddresses() { + final LinkProperties lp = new LinkProperties(); + lp.addLinkAddress(LINKADDRV4); + lp.addLinkAddress(LINKADDRV6); + + final LinkProperties lp2 = new LinkProperties(); + lp2.addLinkAddress(LINKADDRV6); + + final LinkProperties lp3 = new LinkProperties(); + final List linkAddresses = Arrays.asList(LINKADDRV4); + lp3.setLinkAddresses(linkAddresses); + + assertFalse(lp.equals(lp2)); + assertFalse(lp2.equals(lp3)); + + lp.removeLinkAddress(LINKADDRV4); + assertTrue(lp.equals(lp2)); + + lp2.setLinkAddresses(lp3.getLinkAddresses()); + assertTrue(lp2.equals(lp3)); + } + + @Test + public void testNat64Prefix() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.addLinkAddress(LINKADDRV4); + lp.addLinkAddress(LINKADDRV6); + + assertNull(lp.getNat64Prefix()); + + IpPrefix p = new IpPrefix("64:ff9b::/96"); + lp.setNat64Prefix(p); + assertEquals(p, lp.getNat64Prefix()); + + p = new IpPrefix("2001:db8:a:b:1:2:3::/96"); + lp.setNat64Prefix(p); + assertEquals(p, lp.getNat64Prefix()); + + p = new IpPrefix("2001:db8:a:b:1:2::/80"); + try { + lp.setNat64Prefix(p); + } catch (IllegalArgumentException expected) { + } + + p = new IpPrefix("64:ff9b::/64"); + try { + lp.setNat64Prefix(p); + } catch (IllegalArgumentException expected) { + } + + assertEquals(new IpPrefix("2001:db8:a:b:1:2:3::/96"), lp.getNat64Prefix()); + + lp.setNat64Prefix(null); + assertNull(lp.getNat64Prefix()); + } + + @Test + public void testIsProvisioned() { + LinkProperties lp4 = new LinkProperties(); + assertFalse("v4only:empty", lp4.isProvisioned()); + lp4.addLinkAddress(LINKADDRV4); + assertFalse("v4only:addr-only", lp4.isProvisioned()); + lp4.addDnsServer(DNS1); + assertFalse("v4only:addr+dns", lp4.isProvisioned()); + lp4.addRoute(new RouteInfo(GATEWAY1)); + assertTrue("v4only:addr+dns+route", lp4.isProvisioned()); + assertTrue("v4only:addr+dns+route", lp4.isIpv4Provisioned()); + assertFalse("v4only:addr+dns+route", lp4.isIpv6Provisioned()); + + LinkProperties lp6 = new LinkProperties(); + assertFalse("v6only:empty", lp6.isProvisioned()); + lp6.addLinkAddress(LINKADDRV6LINKLOCAL); + assertFalse("v6only:fe80-only", lp6.isProvisioned()); + lp6.addDnsServer(DNS6); + assertFalse("v6only:fe80+dns", lp6.isProvisioned()); + lp6.addRoute(new RouteInfo(GATEWAY61)); + assertFalse("v6only:fe80+dns+route", lp6.isProvisioned()); + lp6.addLinkAddress(LINKADDRV6); + assertTrue("v6only:fe80+global+dns+route", lp6.isIpv6Provisioned()); + assertTrue("v6only:fe80+global+dns+route", lp6.isProvisioned()); + lp6.removeLinkAddress(LINKADDRV6LINKLOCAL); + assertFalse("v6only:global+dns+route", lp6.isIpv4Provisioned()); + assertTrue("v6only:global+dns+route", lp6.isIpv6Provisioned()); + assertTrue("v6only:global+dns+route", lp6.isProvisioned()); + + LinkProperties lp46 = new LinkProperties(); + lp46.addLinkAddress(LINKADDRV4); + lp46.addLinkAddress(LINKADDRV6); + lp46.addDnsServer(DNS1); + lp46.addDnsServer(DNS6); + assertFalse("dualstack:missing-routes", lp46.isProvisioned()); + lp46.addRoute(new RouteInfo(GATEWAY1)); + assertTrue("dualstack:v4-provisioned", lp46.isIpv4Provisioned()); + assertFalse("dualstack:v4-provisioned", lp46.isIpv6Provisioned()); + assertTrue("dualstack:v4-provisioned", lp46.isProvisioned()); + lp46.addRoute(new RouteInfo(GATEWAY61)); + assertTrue("dualstack:both-provisioned", lp46.isIpv4Provisioned()); + assertTrue("dualstack:both-provisioned", lp46.isIpv6Provisioned()); + assertTrue("dualstack:both-provisioned", lp46.isProvisioned()); + + // A link with an IPv6 address and default route, but IPv4 DNS server. + LinkProperties mixed = new LinkProperties(); + mixed.addLinkAddress(LINKADDRV6); + mixed.addDnsServer(DNS1); + mixed.addRoute(new RouteInfo(GATEWAY61)); + assertFalse("mixed:addr6+route6+dns4", mixed.isIpv4Provisioned()); + assertFalse("mixed:addr6+route6+dns4", mixed.isIpv6Provisioned()); + assertFalse("mixed:addr6+route6+dns4", mixed.isProvisioned()); + } + + @Test + public void testCompareProvisioning() { + LinkProperties v4lp = new LinkProperties(); + v4lp.addLinkAddress(LINKADDRV4); + v4lp.addRoute(new RouteInfo(GATEWAY1)); + v4lp.addDnsServer(DNS1); + assertTrue(v4lp.isProvisioned()); + + LinkProperties v4r = new LinkProperties(v4lp); + v4r.removeDnsServer(DNS1); + assertFalse(v4r.isProvisioned()); + + assertEquals(ProvisioningChange.STILL_NOT_PROVISIONED, + LinkProperties.compareProvisioning(v4r, v4r)); + assertEquals(ProvisioningChange.LOST_PROVISIONING, + LinkProperties.compareProvisioning(v4lp, v4r)); + assertEquals(ProvisioningChange.GAINED_PROVISIONING, + LinkProperties.compareProvisioning(v4r, v4lp)); + assertEquals(ProvisioningChange.STILL_PROVISIONED, + LinkProperties.compareProvisioning(v4lp, v4lp)); + + // Check that losing IPv4 provisioning on a dualstack network is + // seen as a total loss of provisioning. + LinkProperties v6lp = new LinkProperties(); + v6lp.addLinkAddress(LINKADDRV6); + v6lp.addRoute(new RouteInfo(GATEWAY61)); + v6lp.addDnsServer(DNS6); + assertFalse(v6lp.isIpv4Provisioned()); + assertTrue(v6lp.isIpv6Provisioned()); + assertTrue(v6lp.isProvisioned()); + + LinkProperties v46lp = new LinkProperties(v6lp); + v46lp.addLinkAddress(LINKADDRV4); + v46lp.addRoute(new RouteInfo(GATEWAY1)); + v46lp.addDnsServer(DNS1); + assertTrue(v46lp.isIpv4Provisioned()); + assertTrue(v46lp.isIpv6Provisioned()); + assertTrue(v46lp.isProvisioned()); + + assertEquals(ProvisioningChange.STILL_PROVISIONED, + LinkProperties.compareProvisioning(v4lp, v46lp)); + assertEquals(ProvisioningChange.STILL_PROVISIONED, + LinkProperties.compareProvisioning(v6lp, v46lp)); + assertEquals(ProvisioningChange.LOST_PROVISIONING, + LinkProperties.compareProvisioning(v46lp, v6lp)); + assertEquals(ProvisioningChange.LOST_PROVISIONING, + LinkProperties.compareProvisioning(v46lp, v4lp)); + + // Check that losing and gaining a secondary router does not change + // the provisioning status. + LinkProperties v6lp2 = new LinkProperties(v6lp); + v6lp2.addRoute(new RouteInfo(GATEWAY62)); + assertTrue(v6lp2.isProvisioned()); + + assertEquals(ProvisioningChange.STILL_PROVISIONED, + LinkProperties.compareProvisioning(v6lp2, v6lp)); + assertEquals(ProvisioningChange.STILL_PROVISIONED, + LinkProperties.compareProvisioning(v6lp, v6lp2)); + } + + @Test + public void testIsReachable() { + final LinkProperties v4lp = new LinkProperties(); + assertFalse(v4lp.isReachable(DNS1)); + assertFalse(v4lp.isReachable(DNS2)); + + // Add an on-link route, making the on-link DNS server reachable, + // but there is still no IPv4 address. + assertTrue(v4lp.addRoute(new RouteInfo(new IpPrefix(address("75.208.0.0"), 16)))); + assertFalse(v4lp.isReachable(DNS1)); + assertFalse(v4lp.isReachable(DNS2)); + + // Adding an IPv4 address (right now, any IPv4 address) means we use + // the routes to compute likely reachability. + assertTrue(v4lp.addLinkAddress(new LinkAddress(ADDRV4, 16))); + assertTrue(v4lp.isReachable(DNS1)); + assertFalse(v4lp.isReachable(DNS2)); + + // Adding a default route makes the off-link DNS server reachable. + assertTrue(v4lp.addRoute(new RouteInfo(GATEWAY1))); + assertTrue(v4lp.isReachable(DNS1)); + assertTrue(v4lp.isReachable(DNS2)); + + final LinkProperties v6lp = new LinkProperties(); + final InetAddress kLinkLocalDns = address("fe80::6:1"); + final InetAddress kLinkLocalDnsWithScope = address("fe80::6:2%43"); + final InetAddress kOnLinkDns = address("2001:db8:85a3::53"); + assertFalse(v6lp.isReachable(kLinkLocalDns)); + assertFalse(v6lp.isReachable(kLinkLocalDnsWithScope)); + assertFalse(v6lp.isReachable(kOnLinkDns)); + assertFalse(v6lp.isReachable(DNS6)); + + // Add a link-local route, making the link-local DNS servers reachable. Because + // we assume the presence of an IPv6 link-local address, link-local DNS servers + // are considered reachable, but only those with a non-zero scope identifier. + assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(address("fe80::"), 64)))); + assertFalse(v6lp.isReachable(kLinkLocalDns)); + assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope)); + assertFalse(v6lp.isReachable(kOnLinkDns)); + assertFalse(v6lp.isReachable(DNS6)); + + // Add a link-local address--nothing changes. + assertTrue(v6lp.addLinkAddress(LINKADDRV6LINKLOCAL)); + assertFalse(v6lp.isReachable(kLinkLocalDns)); + assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope)); + assertFalse(v6lp.isReachable(kOnLinkDns)); + assertFalse(v6lp.isReachable(DNS6)); + + // Add a global route on link, but no global address yet. DNS servers reachable + // via a route that doesn't require a gateway: give them the benefit of the + // doubt and hope the link-local source address suffices for communication. + assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(address("2001:db8:85a3::"), 64)))); + assertFalse(v6lp.isReachable(kLinkLocalDns)); + assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope)); + assertTrue(v6lp.isReachable(kOnLinkDns)); + assertFalse(v6lp.isReachable(DNS6)); + + // Add a global address; the on-link global address DNS server is (still) + // presumed reachable. + assertTrue(v6lp.addLinkAddress(new LinkAddress(ADDRV6, 64))); + assertFalse(v6lp.isReachable(kLinkLocalDns)); + assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope)); + assertTrue(v6lp.isReachable(kOnLinkDns)); + assertFalse(v6lp.isReachable(DNS6)); + + // Adding a default route makes the off-link DNS server reachable. + assertTrue(v6lp.addRoute(new RouteInfo(GATEWAY62))); + assertFalse(v6lp.isReachable(kLinkLocalDns)); + assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope)); + assertTrue(v6lp.isReachable(kOnLinkDns)); + assertTrue(v6lp.isReachable(DNS6)); + + // Check isReachable on stacked links. This requires that the source IP address be assigned + // on the interface returned by the route lookup. + LinkProperties stacked = new LinkProperties(); + + // Can't add a stacked link without an interface name. + stacked.setInterfaceName("v4-test0"); + v6lp.addStackedLink(stacked); + + InetAddress stackedAddress = address("192.0.0.4"); + LinkAddress stackedLinkAddress = new LinkAddress(stackedAddress, 32); + assertFalse(v6lp.isReachable(stackedAddress)); + stacked.addLinkAddress(stackedLinkAddress); + assertFalse(v6lp.isReachable(stackedAddress)); + stacked.addRoute(new RouteInfo(stackedLinkAddress)); + assertTrue(stacked.isReachable(stackedAddress)); + assertTrue(v6lp.isReachable(stackedAddress)); + + assertFalse(v6lp.isReachable(DNS1)); + stacked.addRoute(new RouteInfo((IpPrefix) null, stackedAddress)); + assertTrue(v6lp.isReachable(DNS1)); + } + + @Test + public void testLinkPropertiesEnsureDirectlyConnectedRoutes() { + // IPv4 case: no route added initially + LinkProperties rmnet0 = new LinkProperties(); + rmnet0.setInterfaceName("rmnet0"); + rmnet0.addLinkAddress(new LinkAddress("10.0.0.2/8")); + RouteInfo directRoute0 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null, + rmnet0.getInterfaceName()); + + // Since no routes is added explicitly, getAllRoutes() should return empty. + assertTrue(rmnet0.getAllRoutes().isEmpty()); + rmnet0.ensureDirectlyConnectedRoutes(); + // ensureDirectlyConnectedRoutes() should have added the missing local route. + assertEqualRoutes(Collections.singletonList(directRoute0), rmnet0.getAllRoutes()); + + // IPv4 case: both direct and default routes added initially + LinkProperties rmnet1 = new LinkProperties(); + rmnet1.setInterfaceName("rmnet1"); + rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8")); + RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null, address("10.0.0.1"), + rmnet1.getInterfaceName()); + RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null, + rmnet1.getInterfaceName()); + rmnet1.addRoute(defaultRoute1); + rmnet1.addRoute(directRoute1); + + // Check added routes + assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes()); + // ensureDirectlyConnectedRoutes() shouldn't change the routes since direct connected + // route is already part of the configuration. + rmnet1.ensureDirectlyConnectedRoutes(); + assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes()); + + // IPv6 case: only default routes added initially + LinkProperties rmnet2 = new LinkProperties(); + rmnet2.setInterfaceName("rmnet2"); + rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64")); + rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64")); + RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null, address("2001:db8::1"), + rmnet2.getInterfaceName()); + RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null, + rmnet2.getInterfaceName()); + RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null, + rmnet2.getInterfaceName()); + rmnet2.addRoute(defaultRoute2); + + assertEqualRoutes(Arrays.asList(defaultRoute2), rmnet2.getAllRoutes()); + rmnet2.ensureDirectlyConnectedRoutes(); + assertEqualRoutes(Arrays.asList(defaultRoute2, directRoute2, linkLocalRoute2), + rmnet2.getAllRoutes()); + + // Corner case: no interface name + LinkProperties rmnet3 = new LinkProperties(); + rmnet3.addLinkAddress(new LinkAddress("192.168.0.2/24")); + RouteInfo directRoute3 = new RouteInfo(new IpPrefix("192.168.0.0/24"), null, + rmnet3.getInterfaceName()); + + assertTrue(rmnet3.getAllRoutes().isEmpty()); + rmnet3.ensureDirectlyConnectedRoutes(); + assertEqualRoutes(Collections.singletonList(directRoute3), rmnet3.getAllRoutes()); + } + + private void assertEqualRoutes(Collection expected, Collection actual) { + Set expectedSet = new ArraySet<>(expected); + Set actualSet = new ArraySet<>(actual); + // Duplicated entries in actual routes are considered failures + assertEquals(actual.size(), actualSet.size()); + + assertEquals(expectedSet, actualSet); + } + + private static LinkProperties makeLinkPropertiesForParceling() { + LinkProperties source = new LinkProperties(); + source.setInterfaceName(NAME); + + source.addLinkAddress(LINKADDRV4); + source.addLinkAddress(LINKADDRV6); + + source.addDnsServer(DNS1); + source.addDnsServer(DNS2); + source.addDnsServer(GATEWAY62); + + source.addPcscfServer(TESTIPV4ADDR); + source.addPcscfServer(TESTIPV6ADDR); + + source.setUsePrivateDns(true); + source.setPrivateDnsServerName(PRIV_DNS_SERVER_NAME); + + source.setDomains(DOMAINS); + + source.addRoute(new RouteInfo(GATEWAY1)); + source.addRoute(new RouteInfo(GATEWAY2)); + + source.addValidatedPrivateDnsServer(DNS6); + source.addValidatedPrivateDnsServer(GATEWAY61); + source.addValidatedPrivateDnsServer(TESTIPV6ADDR); + + source.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888)); + + source.setMtu(MTU); + + source.setTcpBufferSizes(TCP_BUFFER_SIZES); + + source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96")); + + final LinkProperties stacked = new LinkProperties(); + stacked.setInterfaceName("test-stacked"); + source.addStackedLink(stacked); + + return source; + } + + @Test @IgnoreAfter(Build.VERSION_CODES.Q) + public void testLinkPropertiesParcelable_Q() throws Exception { + final LinkProperties source = makeLinkPropertiesForParceling(); + assertParcelSane(source, 14 /* fieldCount */); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testLinkPropertiesParcelable() throws Exception { + final LinkProperties source = makeLinkPropertiesForParceling(); + + source.setWakeOnLanSupported(true); + source.setCaptivePortalApiUrl(CAPPORT_API_URL); + source.setCaptivePortalData((CaptivePortalData) getCaptivePortalData()); + source.setDhcpServerAddress((Inet4Address) GATEWAY1); + assertParcelSane(new LinkProperties(source, true /* parcelSensitiveFields */), + 18 /* fieldCount */); + + // Verify that without using a sensitiveFieldsParcelingCopy, sensitive fields are cleared. + final LinkProperties sanitized = new LinkProperties(source); + sanitized.setCaptivePortalApiUrl(null); + sanitized.setCaptivePortalData(null); + assertEquals(sanitized, parcelingRoundTrip(source)); + } + + // Parceling of the scope was broken until Q-QPR2 + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testLinkLocalDnsServerParceling() throws Exception { + final String strAddress = "fe80::1%lo"; + final LinkProperties lp = new LinkProperties(); + lp.addDnsServer(address(strAddress)); + final LinkProperties unparceled = parcelingRoundTrip(lp); + // Inet6Address#equals does not test for the scope id + assertEquals(strAddress, unparceled.getDnsServers().get(0).getHostAddress()); + } + + @Test + public void testParcelUninitialized() throws Exception { + LinkProperties empty = new LinkProperties(); + assertParcelingIsLossless(empty); + } + + @Test + public void testConstructor() { + LinkProperties lp = new LinkProperties(); + checkEmpty(lp); + assertLinkPropertiesEqual(lp, new LinkProperties(lp)); + assertLinkPropertiesEqual(lp, new LinkProperties()); + + lp = makeTestObject(); + assertLinkPropertiesEqual(lp, new LinkProperties(lp)); + } + + @Test + public void testDnsServers() { + final LinkProperties lp = new LinkProperties(); + final List dnsServers = Arrays.asList(DNS1, DNS2); + lp.setDnsServers(dnsServers); + assertEquals(2, lp.getDnsServers().size()); + assertEquals(DNS1, lp.getDnsServers().get(0)); + assertEquals(DNS2, lp.getDnsServers().get(1)); + + lp.removeDnsServer(DNS1); + assertEquals(1, lp.getDnsServers().size()); + assertEquals(DNS2, lp.getDnsServers().get(0)); + + lp.addDnsServer(DNS6); + assertEquals(2, lp.getDnsServers().size()); + assertEquals(DNS2, lp.getDnsServers().get(0)); + assertEquals(DNS6, lp.getDnsServers().get(1)); + } + + @Test + public void testValidatedPrivateDnsServers() { + final LinkProperties lp = new LinkProperties(); + final List privDnsServers = Arrays.asList(PRIVDNS1, PRIVDNS2); + lp.setValidatedPrivateDnsServers(privDnsServers); + assertEquals(2, lp.getValidatedPrivateDnsServers().size()); + assertEquals(PRIVDNS1, lp.getValidatedPrivateDnsServers().get(0)); + assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(1)); + + lp.removeValidatedPrivateDnsServer(PRIVDNS1); + assertEquals(1, lp.getValidatedPrivateDnsServers().size()); + assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(0)); + + lp.addValidatedPrivateDnsServer(PRIVDNS6); + assertEquals(2, lp.getValidatedPrivateDnsServers().size()); + assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(0)); + assertEquals(PRIVDNS6, lp.getValidatedPrivateDnsServers().get(1)); + } + + @Test + public void testPcscfServers() { + final LinkProperties lp = new LinkProperties(); + final List pcscfServers = Arrays.asList(PCSCFV4); + lp.setPcscfServers(pcscfServers); + assertEquals(1, lp.getPcscfServers().size()); + assertEquals(PCSCFV4, lp.getPcscfServers().get(0)); + + lp.removePcscfServer(PCSCFV4); + assertEquals(0, lp.getPcscfServers().size()); + + lp.addPcscfServer(PCSCFV6); + assertEquals(1, lp.getPcscfServers().size()); + assertEquals(PCSCFV6, lp.getPcscfServers().get(0)); + } + + @Test + public void testTcpBufferSizes() { + final LinkProperties lp = makeTestObject(); + assertEquals(TCP_BUFFER_SIZES, lp.getTcpBufferSizes()); + + lp.setTcpBufferSizes(null); + assertNull(lp.getTcpBufferSizes()); + } + + @Test + public void testHasIpv6DefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIPv6DefaultRoute()); + + lp.addRoute(new RouteInfo(GATEWAY61)); + assertTrue(lp.hasIPv6DefaultRoute()); + } + + @Test + public void testHttpProxy() { + final LinkProperties lp = makeTestObject(); + assertTrue(lp.getHttpProxy().equals(ProxyInfo.buildDirectProxy("test", 8888))); + } + + @Test + public void testPrivateDnsServerName() { + final LinkProperties lp = makeTestObject(); + assertEquals(PRIV_DNS_SERVER_NAME, lp.getPrivateDnsServerName()); + + lp.setPrivateDnsServerName(null); + assertNull(lp.getPrivateDnsServerName()); + } + + @Test + public void testUsePrivateDns() { + final LinkProperties lp = makeTestObject(); + assertTrue(lp.isPrivateDnsActive()); + + lp.clear(); + assertFalse(lp.isPrivateDnsActive()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testDhcpServerAddress() { + final LinkProperties lp = makeTestObject(); + assertEquals(DHCPSERVER, lp.getDhcpServerAddress()); + + lp.clear(); + assertNull(lp.getDhcpServerAddress()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testWakeOnLanSupported() { + final LinkProperties lp = makeTestObject(); + assertTrue(lp.isWakeOnLanSupported()); + + lp.clear(); + assertFalse(lp.isWakeOnLanSupported()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testCaptivePortalApiUrl() { + final LinkProperties lp = makeTestObject(); + assertEquals(CAPPORT_API_URL, lp.getCaptivePortalApiUrl()); + + lp.clear(); + assertNull(lp.getCaptivePortalApiUrl()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testCaptivePortalData() { + final LinkProperties lp = makeTestObject(); + assertEquals(getCaptivePortalData(), lp.getCaptivePortalData()); + + lp.clear(); + assertNull(lp.getCaptivePortalData()); + } + + private LinkProperties makeIpv4LinkProperties() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(NAME); + linkProperties.addLinkAddress(LINKADDRV4); + linkProperties.addDnsServer(DNS1); + linkProperties.addRoute(new RouteInfo(GATEWAY1)); + linkProperties.addRoute(new RouteInfo(GATEWAY2)); + return linkProperties; + } + + private LinkProperties makeIpv6LinkProperties() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(NAME); + linkProperties.addLinkAddress(LINKADDRV6); + linkProperties.addDnsServer(DNS6); + linkProperties.addRoute(new RouteInfo(GATEWAY61)); + linkProperties.addRoute(new RouteInfo(GATEWAY62)); + return linkProperties; + } + + @Test + public void testHasIpv4DefaultRoute() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertTrue(Ipv4.hasIpv4DefaultRoute()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertFalse(Ipv6.hasIpv4DefaultRoute()); + } + + @Test + public void testHasIpv4DnsServer() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertTrue(Ipv4.hasIpv4DnsServer()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertFalse(Ipv6.hasIpv4DnsServer()); + } + + @Test + public void testHasIpv6DnsServer() { + final LinkProperties Ipv4 = makeIpv4LinkProperties(); + assertFalse(Ipv4.hasIpv6DnsServer()); + final LinkProperties Ipv6 = makeIpv6LinkProperties(); + assertTrue(Ipv6.hasIpv6DnsServer()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testHasIpv4UnreachableDefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); + assertTrue(lp.hasIpv4UnreachableDefaultRoute()); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testHasIpv6UnreachableDefaultRoute() { + final LinkProperties lp = makeTestObject(); + assertFalse(lp.hasIpv6UnreachableDefaultRoute()); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); + assertTrue(lp.hasIpv6UnreachableDefaultRoute()); + assertFalse(lp.hasIpv4UnreachableDefaultRoute()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testRouteAddWithSameKey() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan0"); + final IpPrefix v6 = new IpPrefix("64:ff9b::/96"); + lp.addRoute(new RouteInfo(v6, address("fe80::1"), "wlan0", RTN_UNICAST, 1280)); + assertEquals(1, lp.getRoutes().size()); + lp.addRoute(new RouteInfo(v6, address("fe80::1"), "wlan0", RTN_UNICAST, 1500)); + assertEquals(1, lp.getRoutes().size()); + final IpPrefix v4 = new IpPrefix("192.0.2.128/25"); + lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_UNICAST, 1460)); + assertEquals(2, lp.getRoutes().size()); + lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_THROW, 1460)); + assertEquals(2, lp.getRoutes().size()); + } +} diff --git a/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt b/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt new file mode 100644 index 0000000000..a5e44d59fc --- /dev/null +++ b/tests/common/java/android/net/MatchAllNetworkSpecifierTest.kt @@ -0,0 +1,75 @@ +/* + * 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 android.net + +import android.net.wifi.aware.DiscoverySession +import android.net.wifi.aware.PeerHandle +import android.net.wifi.aware.WifiAwareNetworkSpecifier +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 + +import com.android.testutils.assertParcelSane +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo + +import java.lang.IllegalStateException + +import org.junit.Assert.assertFalse +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito + +@RunWith(AndroidJUnit4::class) +@SmallTest +class MatchAllNetworkSpecifierTest { + @Rule @JvmField + val ignoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule() + + private val specifier = MatchAllNetworkSpecifier() + private val discoverySession = Mockito.mock(DiscoverySession::class.java) + private val peerHandle = Mockito.mock(PeerHandle::class.java) + private val wifiAwareNetworkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, + peerHandle).build() + + @Test + fun testParcel() { + assertParcelSane(MatchAllNetworkSpecifier(), 0) + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.Q) + @IgnoreAfter(Build.VERSION_CODES.R) + // Only run this test on Android R. + // The method - satisfiedBy() has changed to canBeSatisfiedBy() starting from Android R, so the + // method - canBeSatisfiedBy() cannot be found when running this test on Android Q. + fun testCanBeSatisfiedBy_OnlyForR() { + // MatchAllNetworkSpecifier didn't follow its parent class to change the satisfiedBy() to + // canBeSatisfiedBy(), so if a caller calls MatchAllNetworkSpecifier#canBeSatisfiedBy(), the + // NetworkSpecifier#canBeSatisfiedBy() will be called actually, and false will be returned. + // Although it's not meeting the expectation, the behavior still needs to be verified. + assertFalse(specifier.canBeSatisfiedBy(wifiAwareNetworkSpecifier)) + } + + @Test(expected = IllegalStateException::class) + @IgnoreUpTo(Build.VERSION_CODES.R) + fun testCanBeSatisfiedBy() { + specifier.canBeSatisfiedBy(wifiAwareNetworkSpecifier) + } +} diff --git a/tests/common/java/android/net/NattKeepalivePacketDataTest.kt b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt new file mode 100644 index 0000000000..46f39dd016 --- /dev/null +++ b/tests/common/java/android/net/NattKeepalivePacketDataTest.kt @@ -0,0 +1,114 @@ +/* + * 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 android.net + +import android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS +import android.net.InvalidPacketException.ERROR_INVALID_PORT +import android.net.NattSocketKeepalive.NATT_PORT +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertEqualBothWays +import com.android.testutils.assertFieldCountEquals +import com.android.testutils.assertParcelSane +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.parcelingRoundTrip +import java.net.InetAddress +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Assert.fail +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NattKeepalivePacketDataTest { + @Rule @JvmField + val ignoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule() + + /* Refer to the definition in {@code NattKeepalivePacketData} */ + private val IPV4_HEADER_LENGTH = 20 + private val UDP_HEADER_LENGTH = 8 + + private val TEST_PORT = 4243 + private val TEST_PORT2 = 4244 + private val TEST_SRC_ADDRV4 = "198.168.0.2".address() + private val TEST_DST_ADDRV4 = "198.168.0.1".address() + private val TEST_ADDRV6 = "2001:db8::1".address() + + private fun String.address() = InetAddresses.parseNumericAddress(this) + private fun nattKeepalivePacket( + srcAddress: InetAddress? = TEST_SRC_ADDRV4, + srcPort: Int = TEST_PORT, + dstAddress: InetAddress? = TEST_DST_ADDRV4, + dstPort: Int = NATT_PORT + ) = NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort, dstAddress, dstPort) + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testConstructor() { + try { + nattKeepalivePacket(dstPort = TEST_PORT) + fail("Dst port is not NATT port should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_PORT) + } + + try { + nattKeepalivePacket(srcAddress = TEST_ADDRV6) + fail("A v6 srcAddress should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + nattKeepalivePacket(dstAddress = TEST_ADDRV6) + fail("A v6 dstAddress should cause exception") + } catch (e: InvalidPacketException) { + assertEquals(e.error, ERROR_INVALID_IP_ADDRESS) + } + + try { + parcelingRoundTrip( + NattKeepalivePacketData(TEST_SRC_ADDRV4, TEST_PORT, TEST_DST_ADDRV4, TEST_PORT, + byteArrayOf(12, 31, 22, 44))) + fail("Invalid data should cause exception") + } catch (e: IllegalArgumentException) { } + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testParcel() { + assertParcelSane(nattKeepalivePacket(), 0) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testEquals() { + assertEqualBothWays(nattKeepalivePacket(), nattKeepalivePacket()) + assertNotEquals(nattKeepalivePacket(dstAddress = TEST_SRC_ADDRV4), nattKeepalivePacket()) + assertNotEquals(nattKeepalivePacket(srcAddress = TEST_DST_ADDRV4), nattKeepalivePacket()) + // Test src port only because dst port have to be NATT_PORT + assertNotEquals(nattKeepalivePacket(srcPort = TEST_PORT2), nattKeepalivePacket()) + // Make sure the parceling test is updated if fields are added in the base class. + assertFieldCountEquals(5, KeepalivePacketData::class.java) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testHashCode() { + assertEquals(nattKeepalivePacket().hashCode(), nattKeepalivePacket().hashCode()) + } +} \ No newline at end of file diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt new file mode 100644 index 0000000000..2b45b3d69c --- /dev/null +++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt @@ -0,0 +1,91 @@ +/* + * 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 + +import android.os.Build +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.modules.utils.build.SdkLevel.isAtLeastS +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkAgentConfigTest { + @Rule @JvmField + val ignoreRule = DevSdkIgnoreRule() + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testParcelNetworkAgentConfig() { + val config = NetworkAgentConfig.Builder().apply { + setExplicitlySelected(true) + setLegacyType(ConnectivityManager.TYPE_ETHERNET) + setSubscriberId("MySubId") + setPartialConnectivityAcceptable(false) + setUnvalidatedConnectivityAcceptable(true) + if (isAtLeastS()) { + setBypassableVpn(true) + } + }.build() + if (isAtLeastS()) { + // From S, the config will have 12 items + assertParcelSane(config, 12) + } else { + // For R or below, the config will have 10 items + assertParcelSane(config, 10) + } + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testBuilder() { + val config = NetworkAgentConfig.Builder().apply { + setExplicitlySelected(true) + setLegacyType(ConnectivityManager.TYPE_ETHERNET) + setSubscriberId("MySubId") + setPartialConnectivityAcceptable(false) + setUnvalidatedConnectivityAcceptable(true) + setLegacyTypeName("TEST_NETWORK") + if (isAtLeastS()) { + setNat64DetectionEnabled(false) + setProvisioningNotificationEnabled(false) + setBypassableVpn(true) + } + }.build() + + assertTrue(config.isExplicitlySelected()) + assertEquals(ConnectivityManager.TYPE_ETHERNET, config.getLegacyType()) + assertEquals("MySubId", config.getSubscriberId()) + assertFalse(config.isPartialConnectivityAcceptable()) + assertTrue(config.isUnvalidatedConnectivityAcceptable()) + assertEquals("TEST_NETWORK", config.getLegacyTypeName()) + if (isAtLeastS()) { + assertFalse(config.isNat64DetectionEnabled()) + assertFalse(config.isProvisioningNotificationEnabled()) + assertTrue(config.isBypassableVpn()) + } else { + assertTrue(config.isNat64DetectionEnabled()) + assertTrue(config.isProvisioningNotificationEnabled()) + } + } +} diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java new file mode 100644 index 0000000000..b178bad712 --- /dev/null +++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java @@ -0,0 +1,1152 @@ +/* + * Copyright (C) 2017 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; + +import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; +import static android.net.NetworkCapabilities.MAX_TRANSPORT; +import static android.net.NetworkCapabilities.MIN_TRANSPORT; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; +import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION; +import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS; +import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; +import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; +import static android.os.Process.INVALID_UID; + +import static com.android.modules.utils.build.SdkLevel.isAtLeastR; +import static com.android.modules.utils.build.SdkLevel.isAtLeastS; +import static com.android.testutils.MiscAsserts.assertEmpty; +import static com.android.testutils.MiscAsserts.assertThrows; +import static com.android.testutils.ParcelUtils.assertParcelSane; +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import android.net.wifi.aware.DiscoverySession; +import android.net.wifi.aware.PeerHandle; +import android.net.wifi.aware.WifiAwareNetworkSpecifier; +import android.os.Build; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.ArraySet; +import android.util.Range; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.CompatUtil; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkCapabilitiesTest { + private static final String TEST_SSID = "TEST_SSID"; + private static final String DIFFERENT_TEST_SSID = "DIFFERENT_TEST_SSID"; + private static final int TEST_SUBID1 = 1; + private static final int TEST_SUBID2 = 2; + private static final int TEST_SUBID3 = 3; + + @Rule + public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); + + private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class); + private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class); + + @Test + public void testMaybeMarkCapabilitiesRestricted() { + // check that internet does not get restricted + NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.maybeMarkCapabilitiesRestricted(); + assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // metered-ness shouldn't matter + netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.addCapability(NET_CAPABILITY_NOT_METERED); + netCap.maybeMarkCapabilitiesRestricted(); + assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.removeCapability(NET_CAPABILITY_NOT_METERED); + netCap.maybeMarkCapabilitiesRestricted(); + assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // add EIMS - bundled with unrestricted means it's unrestricted + netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.addCapability(NET_CAPABILITY_EIMS); + netCap.addCapability(NET_CAPABILITY_NOT_METERED); + netCap.maybeMarkCapabilitiesRestricted(); + assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.addCapability(NET_CAPABILITY_EIMS); + netCap.removeCapability(NET_CAPABILITY_NOT_METERED); + netCap.maybeMarkCapabilitiesRestricted(); + assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // just a restricted cap should be restricted regardless of meteredness + netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_EIMS); + netCap.addCapability(NET_CAPABILITY_NOT_METERED); + netCap.maybeMarkCapabilitiesRestricted(); + assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_EIMS); + netCap.removeCapability(NET_CAPABILITY_NOT_METERED); + netCap.maybeMarkCapabilitiesRestricted(); + assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // try 2 restricted caps + netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_CBS); + netCap.addCapability(NET_CAPABILITY_EIMS); + netCap.addCapability(NET_CAPABILITY_NOT_METERED); + netCap.maybeMarkCapabilitiesRestricted(); + assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_CBS); + netCap.addCapability(NET_CAPABILITY_EIMS); + netCap.removeCapability(NET_CAPABILITY_NOT_METERED); + netCap.maybeMarkCapabilitiesRestricted(); + assertFalse(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + } + + @Test + public void testDescribeImmutableDifferences() { + NetworkCapabilities nc1; + NetworkCapabilities nc2; + + // Transports changing + nc1 = new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR); + nc2 = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI); + assertNotEquals("", nc1.describeImmutableDifferences(nc2)); + assertEquals("", nc1.describeImmutableDifferences(nc1)); + + // Mutable capability changing + nc1 = new NetworkCapabilities().addCapability(NET_CAPABILITY_VALIDATED); + nc2 = new NetworkCapabilities(); + assertEquals("", nc1.describeImmutableDifferences(nc2)); + assertEquals("", nc1.describeImmutableDifferences(nc1)); + + // NOT_METERED changing (http://b/63326103) + nc1 = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_NOT_METERED) + .addCapability(NET_CAPABILITY_INTERNET); + nc2 = new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET); + assertEquals("", nc1.describeImmutableDifferences(nc2)); + assertEquals("", nc1.describeImmutableDifferences(nc1)); + + // Immutable capability changing + nc1 = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + nc2 = new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET); + assertNotEquals("", nc1.describeImmutableDifferences(nc2)); + assertEquals("", nc1.describeImmutableDifferences(nc1)); + + // Specifier changing + nc1 = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI); + nc2 = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier("eth42")); + assertNotEquals("", nc1.describeImmutableDifferences(nc2)); + assertEquals("", nc1.describeImmutableDifferences(nc1)); + } + + @Test + public void testLinkBandwidthUtils() { + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, NetworkCapabilities + .minBandwidth(LINK_BANDWIDTH_UNSPECIFIED, LINK_BANDWIDTH_UNSPECIFIED)); + assertEquals(10, NetworkCapabilities + .minBandwidth(LINK_BANDWIDTH_UNSPECIFIED, 10)); + assertEquals(10, NetworkCapabilities + .minBandwidth(10, LINK_BANDWIDTH_UNSPECIFIED)); + assertEquals(10, NetworkCapabilities + .minBandwidth(10, 20)); + + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, NetworkCapabilities + .maxBandwidth(LINK_BANDWIDTH_UNSPECIFIED, LINK_BANDWIDTH_UNSPECIFIED)); + assertEquals(10, NetworkCapabilities + .maxBandwidth(LINK_BANDWIDTH_UNSPECIFIED, 10)); + assertEquals(10, NetworkCapabilities + .maxBandwidth(10, LINK_BANDWIDTH_UNSPECIFIED)); + assertEquals(20, NetworkCapabilities + .maxBandwidth(10, 20)); + } + + @Test + public void testSetUids() { + final NetworkCapabilities netCap = new NetworkCapabilities(); + // Null uids match all UIDs + netCap.setUids(null); + assertTrue(netCap.appliesToUid(10)); + assertTrue(netCap.appliesToUid(200)); + assertTrue(netCap.appliesToUid(3000)); + assertTrue(netCap.appliesToUid(10010)); + assertTrue(netCap.appliesToUidRange(new UidRange(50, 100))); + assertTrue(netCap.appliesToUidRange(new UidRange(70, 72))); + assertTrue(netCap.appliesToUidRange(new UidRange(3500, 3912))); + assertTrue(netCap.appliesToUidRange(new UidRange(1, 100000))); + + if (isAtLeastS()) { + final Set> uids = new ArraySet<>(); + uids.add(uidRange(50, 100)); + uids.add(uidRange(3000, 4000)); + netCap.setUids(uids); + assertTrue(netCap.appliesToUid(50)); + assertTrue(netCap.appliesToUid(80)); + assertTrue(netCap.appliesToUid(100)); + assertTrue(netCap.appliesToUid(3000)); + assertTrue(netCap.appliesToUid(3001)); + assertFalse(netCap.appliesToUid(10)); + assertFalse(netCap.appliesToUid(25)); + assertFalse(netCap.appliesToUid(49)); + assertFalse(netCap.appliesToUid(101)); + assertFalse(netCap.appliesToUid(2000)); + assertFalse(netCap.appliesToUid(100000)); + + assertTrue(netCap.appliesToUidRange(new UidRange(50, 100))); + assertTrue(netCap.appliesToUidRange(new UidRange(70, 72))); + assertTrue(netCap.appliesToUidRange(new UidRange(3500, 3912))); + assertFalse(netCap.appliesToUidRange(new UidRange(1, 100))); + assertFalse(netCap.appliesToUidRange(new UidRange(49, 100))); + assertFalse(netCap.appliesToUidRange(new UidRange(1, 10))); + assertFalse(netCap.appliesToUidRange(new UidRange(60, 101))); + assertFalse(netCap.appliesToUidRange(new UidRange(60, 3400))); + + NetworkCapabilities netCap2 = new NetworkCapabilities(); + // A new netcap object has null UIDs, so anything will satisfy it. + assertTrue(netCap2.satisfiedByUids(netCap)); + // Still not equal though. + assertFalse(netCap2.equalsUids(netCap)); + netCap2.setUids(uids); + assertTrue(netCap2.satisfiedByUids(netCap)); + assertTrue(netCap.equalsUids(netCap2)); + assertTrue(netCap2.equalsUids(netCap)); + + uids.add(uidRange(600, 700)); + netCap2.setUids(uids); + assertFalse(netCap2.satisfiedByUids(netCap)); + assertFalse(netCap.appliesToUid(650)); + assertTrue(netCap2.appliesToUid(650)); + netCap.combineCapabilities(netCap2); + assertTrue(netCap2.satisfiedByUids(netCap)); + assertTrue(netCap.appliesToUid(650)); + assertFalse(netCap.appliesToUid(500)); + + assertTrue(new NetworkCapabilities().satisfiedByUids(netCap)); + netCap.combineCapabilities(new NetworkCapabilities()); + assertTrue(netCap.appliesToUid(500)); + assertTrue(netCap.appliesToUidRange(new UidRange(1, 100000))); + assertFalse(netCap2.appliesToUid(500)); + assertFalse(netCap2.appliesToUidRange(new UidRange(1, 100000))); + assertTrue(new NetworkCapabilities().satisfiedByUids(netCap)); + + // Null uids satisfies everything. + netCap.setUids(null); + assertTrue(netCap2.satisfiedByUids(netCap)); + assertTrue(netCap.satisfiedByUids(netCap2)); + netCap2.setUids(null); + assertTrue(netCap2.satisfiedByUids(netCap)); + assertTrue(netCap.satisfiedByUids(netCap2)); + } + } + + @Test + public void testParcelNetworkCapabilities() { + final Set> uids = new ArraySet<>(); + uids.add(uidRange(50, 100)); + uids.add(uidRange(3000, 4000)); + final NetworkCapabilities netCap = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_NOT_METERED); + if (isAtLeastS()) { + netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)); + netCap.setUids(uids); + } + if (isAtLeastR()) { + netCap.setOwnerUid(123); + netCap.setAdministratorUids(new int[] {5, 11}); + } + assertParcelingIsLossless(netCap); + netCap.setSSID(TEST_SSID); + testParcelSane(netCap); + } + + @Test + public void testParcelNetworkCapabilitiesWithRequestorUidAndPackageName() { + final NetworkCapabilities netCap = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_NOT_METERED); + if (isAtLeastR()) { + netCap.setRequestorPackageName("com.android.test"); + netCap.setRequestorUid(9304); + } + assertParcelingIsLossless(netCap); + netCap.setSSID(TEST_SSID); + testParcelSane(netCap); + } + + private void testParcelSane(NetworkCapabilities cap) { + if (isAtLeastS()) { + assertParcelSane(cap, 17); + } else if (isAtLeastR()) { + assertParcelSane(cap, 15); + } else { + assertParcelSane(cap, 11); + } + } + + private static NetworkCapabilities createNetworkCapabilitiesWithTransportInfo() { + return new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_NOT_METERED) + .setSSID(TEST_SSID) + .setTransportInfo(new TestTransportInfo()) + .setRequestorPackageName("com.android.test") + .setRequestorUid(9304); + } + + @Test + public void testNetworkCapabilitiesCopyWithNoRedactions() { + assumeTrue(isAtLeastS()); + + final NetworkCapabilities netCap = createNetworkCapabilitiesWithTransportInfo(); + final NetworkCapabilities netCapWithNoRedactions = + new NetworkCapabilities(netCap, NetworkCapabilities.REDACT_NONE); + TestTransportInfo testTransportInfo = + (TestTransportInfo) netCapWithNoRedactions.getTransportInfo(); + assertFalse(testTransportInfo.locationRedacted); + assertFalse(testTransportInfo.localMacAddressRedacted); + assertFalse(testTransportInfo.settingsRedacted); + } + + @Test + public void testNetworkCapabilitiesCopyWithoutLocationSensitiveFields() { + assumeTrue(isAtLeastS()); + + final NetworkCapabilities netCap = createNetworkCapabilitiesWithTransportInfo(); + final NetworkCapabilities netCapWithNoRedactions = + new NetworkCapabilities(netCap, REDACT_FOR_ACCESS_FINE_LOCATION); + TestTransportInfo testTransportInfo = + (TestTransportInfo) netCapWithNoRedactions.getTransportInfo(); + assertTrue(testTransportInfo.locationRedacted); + assertFalse(testTransportInfo.localMacAddressRedacted); + assertFalse(testTransportInfo.settingsRedacted); + } + + @Test + public void testOemPaid() { + NetworkCapabilities nc = new NetworkCapabilities(); + // By default OEM_PAID is neither in the required or forbidden lists and the network is not + // restricted. + if (isAtLeastS()) { + assertFalse(nc.hasForbiddenCapability(NET_CAPABILITY_OEM_PAID)); + } + assertFalse(nc.hasCapability(NET_CAPABILITY_OEM_PAID)); + nc.maybeMarkCapabilitiesRestricted(); + assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // Adding OEM_PAID to capability list should make network restricted. + nc.addCapability(NET_CAPABILITY_OEM_PAID); + nc.addCapability(NET_CAPABILITY_INTERNET); // Combine with unrestricted capability. + nc.maybeMarkCapabilitiesRestricted(); + assertTrue(nc.hasCapability(NET_CAPABILITY_OEM_PAID)); + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // Now let's make request for OEM_PAID network. + NetworkCapabilities nr = new NetworkCapabilities(); + nr.addCapability(NET_CAPABILITY_OEM_PAID); + nr.maybeMarkCapabilitiesRestricted(); + assertTrue(nr.satisfiedByNetworkCapabilities(nc)); + + // Request fails for network with the default capabilities. + assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities())); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testOemPrivate() { + NetworkCapabilities nc = new NetworkCapabilities(); + // By default OEM_PRIVATE is neither in the required or forbidden lists and the network is + // not restricted. + assertFalse(nc.hasForbiddenCapability(NET_CAPABILITY_OEM_PRIVATE)); + assertFalse(nc.hasCapability(NET_CAPABILITY_OEM_PRIVATE)); + nc.maybeMarkCapabilitiesRestricted(); + assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // Adding OEM_PRIVATE to capability list should make network restricted. + nc.addCapability(NET_CAPABILITY_OEM_PRIVATE); + nc.addCapability(NET_CAPABILITY_INTERNET); // Combine with unrestricted capability. + nc.maybeMarkCapabilitiesRestricted(); + assertTrue(nc.hasCapability(NET_CAPABILITY_OEM_PRIVATE)); + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // Now let's make request for OEM_PRIVATE network. + NetworkCapabilities nr = new NetworkCapabilities(); + nr.addCapability(NET_CAPABILITY_OEM_PRIVATE); + nr.maybeMarkCapabilitiesRestricted(); + assertTrue(nr.satisfiedByNetworkCapabilities(nc)); + + // Request fails for network with the default capabilities. + assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities())); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testForbiddenCapabilities() { + NetworkCapabilities network = new NetworkCapabilities(); + + NetworkCapabilities request = new NetworkCapabilities(); + assertTrue("Request: " + request + ", Network:" + network, + request.satisfiedByNetworkCapabilities(network)); + + // Requesting absence of capabilities that network doesn't have. Request should satisfy. + request.addForbiddenCapability(NET_CAPABILITY_WIFI_P2P); + request.addForbiddenCapability(NET_CAPABILITY_NOT_METERED); + assertTrue(request.satisfiedByNetworkCapabilities(network)); + assertArrayEquals(new int[]{NET_CAPABILITY_WIFI_P2P, + NET_CAPABILITY_NOT_METERED}, + request.getForbiddenCapabilities()); + + // This is a default capability, just want to make sure its there because we use it below. + assertTrue(network.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // Verify that adding forbidden capability will effectively remove it from capability list. + request.addForbiddenCapability(NET_CAPABILITY_NOT_RESTRICTED); + assertTrue(request.hasForbiddenCapability(NET_CAPABILITY_NOT_RESTRICTED)); + assertFalse(request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + // Now this request won't be satisfied because network contains NOT_RESTRICTED. + assertFalse(request.satisfiedByNetworkCapabilities(network)); + network.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + assertTrue(request.satisfiedByNetworkCapabilities(network)); + + // Verify that adding capability will effectively remove it from forbidden list + request.addCapability(NET_CAPABILITY_NOT_RESTRICTED); + assertTrue(request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + assertFalse(request.hasForbiddenCapability(NET_CAPABILITY_NOT_RESTRICTED)); + + assertFalse(request.satisfiedByNetworkCapabilities(network)); + network.addCapability(NET_CAPABILITY_NOT_RESTRICTED); + assertTrue(request.satisfiedByNetworkCapabilities(network)); + } + + @Test + public void testConnectivityManagedCapabilities() { + NetworkCapabilities nc = new NetworkCapabilities(); + assertFalse(nc.hasConnectivityManagedCapability()); + // Check every single system managed capability. + nc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + assertTrue(nc.hasConnectivityManagedCapability()); + nc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + nc.addCapability(NET_CAPABILITY_FOREGROUND); + assertTrue(nc.hasConnectivityManagedCapability()); + nc.removeCapability(NET_CAPABILITY_FOREGROUND); + nc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); + assertTrue(nc.hasConnectivityManagedCapability()); + nc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY); + nc.addCapability(NET_CAPABILITY_VALIDATED); + assertTrue(nc.hasConnectivityManagedCapability()); + } + + @Test + public void testEqualsNetCapabilities() { + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + assertTrue(nc1.equalsNetCapabilities(nc2)); + assertEquals(nc1, nc2); + + nc1.addCapability(NET_CAPABILITY_MMS); + assertFalse(nc1.equalsNetCapabilities(nc2)); + assertNotEquals(nc1, nc2); + nc2.addCapability(NET_CAPABILITY_MMS); + assertTrue(nc1.equalsNetCapabilities(nc2)); + assertEquals(nc1, nc2); + + if (isAtLeastS()) { + nc1.addForbiddenCapability(NET_CAPABILITY_INTERNET); + assertFalse(nc1.equalsNetCapabilities(nc2)); + nc2.addForbiddenCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc1.equalsNetCapabilities(nc2)); + + // Remove a required capability doesn't affect forbidden capabilities. + // This is a behaviour change from R to S. + nc1.removeCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc1.equalsNetCapabilities(nc2)); + + nc1.removeForbiddenCapability(NET_CAPABILITY_INTERNET); + assertFalse(nc1.equalsNetCapabilities(nc2)); + nc2.removeForbiddenCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc1.equalsNetCapabilities(nc2)); + } + } + + @Test + public void testSSID() { + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + assertTrue(nc2.satisfiedBySSID(nc1)); + + nc1.setSSID(TEST_SSID); + assertTrue(nc2.satisfiedBySSID(nc1)); + nc2.setSSID("different " + TEST_SSID); + assertFalse(nc2.satisfiedBySSID(nc1)); + + assertTrue(nc1.satisfiedByImmutableNetworkCapabilities(nc2)); + assertFalse(nc1.satisfiedByNetworkCapabilities(nc2)); + } + + private ArraySet> uidRanges(int from, int to) { + final ArraySet> range = new ArraySet<>(1); + range.add(uidRange(from, to)); + return range; + } + + private Range uidRange(int from, int to) { + return new Range(from, to); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testSetAdministratorUids() { + NetworkCapabilities nc = + new NetworkCapabilities().setAdministratorUids(new int[] {2, 1, 3}); + + assertArrayEquals(new int[] {1, 2, 3}, nc.getAdministratorUids()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testSetAdministratorUidsWithDuplicates() { + try { + new NetworkCapabilities().setAdministratorUids(new int[] {1, 1}); + fail("Expected IllegalArgumentException for duplicate uids"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testCombineCapabilities() { + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + + if (isAtLeastS()) { + nc1.addForbiddenCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + } + nc1.addCapability(NET_CAPABILITY_NOT_ROAMING); + assertNotEquals(nc1, nc2); + nc2.combineCapabilities(nc1); + assertEquals(nc1, nc2); + assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + if (isAtLeastS()) { + assertTrue(nc2.hasForbiddenCapability(NET_CAPABILITY_CAPTIVE_PORTAL)); + } + + if (isAtLeastS()) { + // This will effectively move NOT_ROAMING capability from required to forbidden for nc1. + nc1.addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING); + // It is not allowed to have the same capability in both wanted and forbidden list. + assertThrows(IllegalArgumentException.class, () -> nc2.combineCapabilities(nc1)); + // Remove forbidden capability to continue other tests. + nc1.removeForbiddenCapability(NET_CAPABILITY_NOT_ROAMING); + } + + nc1.setSSID(TEST_SSID); + nc2.combineCapabilities(nc1); + if (isAtLeastR()) { + assertTrue(TEST_SSID.equals(nc2.getSsid())); + } + + // Because they now have the same SSID, the following call should not throw + nc2.combineCapabilities(nc1); + + nc1.setSSID(DIFFERENT_TEST_SSID); + try { + nc2.combineCapabilities(nc1); + fail("Expected IllegalStateException: can't combine different SSIDs"); + } catch (IllegalStateException expected) {} + nc1.setSSID(TEST_SSID); + + if (isAtLeastS()) { + nc1.setUids(uidRanges(10, 13)); + assertNotEquals(nc1, nc2); + nc2.combineCapabilities(nc1); // Everything + 10~13 is still everything. + assertNotEquals(nc1, nc2); + nc1.combineCapabilities(nc2); // 10~13 + everything is everything. + assertEquals(nc1, nc2); + nc1.setUids(uidRanges(10, 13)); + nc2.setUids(uidRanges(20, 23)); + assertNotEquals(nc1, nc2); + nc1.combineCapabilities(nc2); + assertTrue(nc1.appliesToUid(12)); + assertFalse(nc2.appliesToUid(12)); + assertTrue(nc1.appliesToUid(22)); + assertTrue(nc2.appliesToUid(22)); + + // Verify the subscription id list can be combined only when they are equal. + nc1.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)); + nc2.setSubscriptionIds(Set.of(TEST_SUBID2)); + assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1)); + + nc2.setSubscriptionIds(Set.of()); + assertThrows(IllegalStateException.class, () -> nc2.combineCapabilities(nc1)); + + nc2.setSubscriptionIds(Set.of(TEST_SUBID2, TEST_SUBID1)); + nc2.combineCapabilities(nc1); + assertEquals(Set.of(TEST_SUBID2, TEST_SUBID1), nc2.getSubscriptionIds()); + } + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testCombineCapabilities_AdministratorUids() { + final NetworkCapabilities nc1 = new NetworkCapabilities(); + final NetworkCapabilities nc2 = new NetworkCapabilities(); + + final int[] adminUids = {3, 6, 12}; + nc1.setAdministratorUids(adminUids); + nc2.combineCapabilities(nc1); + assertTrue(nc2.equalsAdministratorUids(nc1)); + assertArrayEquals(nc2.getAdministratorUids(), adminUids); + + final int[] adminUidsOtherOrder = {3, 12, 6}; + nc1.setAdministratorUids(adminUidsOtherOrder); + assertTrue(nc2.equalsAdministratorUids(nc1)); + + final int[] adminUids2 = {11, 1, 12, 3, 6}; + nc1.setAdministratorUids(adminUids2); + assertFalse(nc2.equalsAdministratorUids(nc1)); + assertFalse(Arrays.equals(nc2.getAdministratorUids(), adminUids2)); + try { + nc2.combineCapabilities(nc1); + fail("Shouldn't be able to combine different lists of admin UIDs"); + } catch (IllegalStateException expected) { } + } + + @Test + public void testSetCapabilities() { + final int[] REQUIRED_CAPABILITIES = new int[] { + NET_CAPABILITY_INTERNET, NET_CAPABILITY_NOT_VPN }; + + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + + nc1.setCapabilities(REQUIRED_CAPABILITIES); + assertArrayEquals(REQUIRED_CAPABILITIES, nc1.getCapabilities()); + + // Verify that setting and adding capabilities leads to the same object state. + nc2.clearAll(); + for (int cap : REQUIRED_CAPABILITIES) { + nc2.addCapability(cap); + } + assertEquals(nc1, nc2); + + if (isAtLeastS()) { + final int[] forbiddenCapabilities = new int[]{ + NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_RESTRICTED }; + + nc1.setCapabilities(REQUIRED_CAPABILITIES, forbiddenCapabilities); + assertArrayEquals(REQUIRED_CAPABILITIES, nc1.getCapabilities()); + assertArrayEquals(forbiddenCapabilities, nc1.getForbiddenCapabilities()); + + nc2.clearAll(); + for (int cap : REQUIRED_CAPABILITIES) { + nc2.addCapability(cap); + } + for (int cap : forbiddenCapabilities) { + nc2.addForbiddenCapability(cap); + } + assertEquals(nc1, nc2); + } + } + + @Test + public void testSetNetworkSpecifierOnMultiTransportNc() { + // Sequence 1: Transport + Transport + NetworkSpecifier + NetworkCapabilities nc1 = new NetworkCapabilities(); + nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI); + try { + nc1.setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier("eth0")); + fail("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!"); + } catch (IllegalStateException expected) { + // empty + } + + // Sequence 2: Transport + NetworkSpecifier + Transport + NetworkCapabilities nc2 = new NetworkCapabilities(); + nc2.addTransportType(TRANSPORT_CELLULAR).setNetworkSpecifier( + CompatUtil.makeEthernetNetworkSpecifier("testtap3")); + try { + nc2.addTransportType(TRANSPORT_WIFI); + fail("Cannot set a second TransportType of a network which has a NetworkSpecifier!"); + } catch (IllegalStateException expected) { + // empty + } + } + + @Test + public void testSetTransportInfoOnMultiTransportNc() { + // Sequence 1: Transport + Transport + TransportInfo + NetworkCapabilities nc1 = new NetworkCapabilities(); + nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI) + .setTransportInfo(new TestTransportInfo()); + + // Sequence 2: Transport + NetworkSpecifier + Transport + NetworkCapabilities nc2 = new NetworkCapabilities(); + nc2.addTransportType(TRANSPORT_CELLULAR).setTransportInfo(new TestTransportInfo()) + .addTransportType(TRANSPORT_WIFI); + } + + @Test + public void testCombineTransportInfo() { + NetworkCapabilities nc1 = new NetworkCapabilities(); + nc1.setTransportInfo(new TestTransportInfo()); + + NetworkCapabilities nc2 = new NetworkCapabilities(); + // new TransportInfo so that object is not #equals to nc1's TransportInfo (that's where + // combine fails) + nc2.setTransportInfo(new TestTransportInfo()); + + try { + nc1.combineCapabilities(nc2); + fail("Should not be able to combine NetworkCabilities which contain TransportInfos"); + } catch (IllegalStateException expected) { + // empty + } + + // verify that can combine with identical TransportInfo objects + NetworkCapabilities nc3 = new NetworkCapabilities(); + nc3.setTransportInfo(nc1.getTransportInfo()); + nc1.combineCapabilities(nc3); + } + + @Test + public void testSet() { + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + + if (isAtLeastS()) { + nc1.addForbiddenCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + } + nc1.addCapability(NET_CAPABILITY_NOT_ROAMING); + assertNotEquals(nc1, nc2); + nc2.set(nc1); + assertEquals(nc1, nc2); + assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + if (isAtLeastS()) { + assertTrue(nc2.hasForbiddenCapability(NET_CAPABILITY_CAPTIVE_PORTAL)); + } + + if (isAtLeastS()) { + // This will effectively move NOT_ROAMING capability from required to forbidden for nc1. + nc1.addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING); + } + nc1.setSSID(TEST_SSID); + nc2.set(nc1); + assertEquals(nc1, nc2); + if (isAtLeastS()) { + // Contrary to combineCapabilities, set() will have removed the NOT_ROAMING capability + // from nc2. + assertFalse(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + assertTrue(nc2.hasForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)); + } + + if (isAtLeastR()) { + assertTrue(TEST_SSID.equals(nc2.getSsid())); + } + + nc1.setSSID(DIFFERENT_TEST_SSID); + nc2.set(nc1); + assertEquals(nc1, nc2); + if (isAtLeastR()) { + assertTrue(DIFFERENT_TEST_SSID.equals(nc2.getSsid())); + } + if (isAtLeastS()) { + nc1.setUids(uidRanges(10, 13)); + } else { + nc1.setUids(null); + } + nc2.set(nc1); // Overwrites, as opposed to combineCapabilities + assertEquals(nc1, nc2); + + if (isAtLeastS()) { + assertThrows(NullPointerException.class, () -> nc1.setSubscriptionIds(null)); + nc1.setSubscriptionIds(Set.of()); + nc2.set(nc1); + assertEquals(nc1, nc2); + + nc1.setSubscriptionIds(Set.of(TEST_SUBID1)); + nc2.set(nc1); + assertEquals(nc1, nc2); + + nc2.setSubscriptionIds(Set.of(TEST_SUBID2, TEST_SUBID1)); + nc2.set(nc1); + assertEquals(nc1, nc2); + + nc2.setSubscriptionIds(Set.of(TEST_SUBID3, TEST_SUBID2)); + assertNotEquals(nc1, nc2); + } + } + + @Test + public void testGetTransportTypes() { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.addTransportType(TRANSPORT_CELLULAR); + nc.addTransportType(TRANSPORT_WIFI); + nc.addTransportType(TRANSPORT_VPN); + nc.addTransportType(TRANSPORT_TEST); + + final int[] transportTypes = nc.getTransportTypes(); + assertEquals(4, transportTypes.length); + assertEquals(TRANSPORT_CELLULAR, transportTypes[0]); + assertEquals(TRANSPORT_WIFI, transportTypes[1]); + assertEquals(TRANSPORT_VPN, transportTypes[2]); + assertEquals(TRANSPORT_TEST, transportTypes[3]); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testTelephonyNetworkSpecifier() { + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1); + final NetworkCapabilities nc1 = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .setNetworkSpecifier(specifier) + .build(); + assertEquals(specifier, nc1.getNetworkSpecifier()); + try { + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setNetworkSpecifier(specifier) + .build(); + fail("Must have a single transport type. Without transport type or multiple transport" + + " types is invalid."); + } catch (IllegalStateException expected) { } + } + + @Test + public void testWifiAwareNetworkSpecifier() { + final NetworkCapabilities nc = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI_AWARE); + // If NetworkSpecifier is not set, the default value is null. + assertNull(nc.getNetworkSpecifier()); + final WifiAwareNetworkSpecifier specifier = new WifiAwareNetworkSpecifier.Builder( + mDiscoverySession, mPeerHandle).build(); + nc.setNetworkSpecifier(specifier); + assertEquals(specifier, nc.getNetworkSpecifier()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testAdministratorUidsAndOwnerUid() { + // Test default owner uid. + // If the owner uid is not set, the default value should be Process.INVALID_UID. + final NetworkCapabilities nc1 = new NetworkCapabilities.Builder().build(); + assertEquals(INVALID_UID, nc1.getOwnerUid()); + // Test setAdministratorUids and getAdministratorUids. + final int[] administratorUids = {1001, 10001}; + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setAdministratorUids(administratorUids) + .build(); + assertTrue(Arrays.equals(administratorUids, nc2.getAdministratorUids())); + // Test setOwnerUid and getOwnerUid. + // The owner UID must be included in administrator UIDs, or throw IllegalStateException. + try { + final NetworkCapabilities nc3 = new NetworkCapabilities.Builder() + .setOwnerUid(1001) + .build(); + fail("The owner UID must be included in administrator UIDs."); + } catch (IllegalStateException expected) { } + final NetworkCapabilities nc4 = new NetworkCapabilities.Builder() + .setAdministratorUids(administratorUids) + .setOwnerUid(1001) + .build(); + assertEquals(1001, nc4.getOwnerUid()); + try { + final NetworkCapabilities nc5 = new NetworkCapabilities.Builder() + .setAdministratorUids(null) + .build(); + fail("Should not set null into setAdministratorUids"); + } catch (NullPointerException expected) { } + } + + private static NetworkCapabilities capsWithSubIds(Integer ... subIds) { + // Since the NetworkRequest would put NOT_VCN_MANAGED capabilities in general, for + // every NetworkCapabilities that simulates networks needs to add it too in order to + // satisfy these requests. + final NetworkCapabilities nc = new NetworkCapabilities.Builder() + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .setSubscriptionIds(new ArraySet<>(subIds)).build(); + assertEquals(new ArraySet<>(subIds), nc.getSubscriptionIds()); + return nc; + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testSubIds() throws Exception { + final NetworkCapabilities ncWithoutId = capsWithSubIds(); + final NetworkCapabilities ncWithId = capsWithSubIds(TEST_SUBID1); + final NetworkCapabilities ncWithOtherIds = capsWithSubIds(TEST_SUBID1, TEST_SUBID3); + final NetworkCapabilities ncWithoutRequestedIds = capsWithSubIds(TEST_SUBID3); + + final NetworkRequest requestWithoutId = new NetworkRequest.Builder().build(); + assertEmpty(requestWithoutId.networkCapabilities.getSubscriptionIds()); + final NetworkRequest requestWithIds = new NetworkRequest.Builder() + .setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)).build(); + assertEquals(Set.of(TEST_SUBID1, TEST_SUBID2), + requestWithIds.networkCapabilities.getSubscriptionIds()); + + assertFalse(requestWithIds.canBeSatisfiedBy(ncWithoutId)); + assertTrue(requestWithIds.canBeSatisfiedBy(ncWithOtherIds)); + assertFalse(requestWithIds.canBeSatisfiedBy(ncWithoutRequestedIds)); + assertTrue(requestWithIds.canBeSatisfiedBy(ncWithId)); + assertTrue(requestWithoutId.canBeSatisfiedBy(ncWithoutId)); + assertTrue(requestWithoutId.canBeSatisfiedBy(ncWithId)); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testEqualsSubIds() throws Exception { + assertEquals(capsWithSubIds(), capsWithSubIds()); + assertNotEquals(capsWithSubIds(), capsWithSubIds(TEST_SUBID1)); + assertEquals(capsWithSubIds(TEST_SUBID1), capsWithSubIds(TEST_SUBID1)); + assertNotEquals(capsWithSubIds(TEST_SUBID1), capsWithSubIds(TEST_SUBID2)); + assertNotEquals(capsWithSubIds(TEST_SUBID1), capsWithSubIds(TEST_SUBID2, TEST_SUBID1)); + assertEquals(capsWithSubIds(TEST_SUBID1, TEST_SUBID2), + capsWithSubIds(TEST_SUBID2, TEST_SUBID1)); + } + + @Test + public void testLinkBandwidthKbps() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // The default value of LinkDown/UpstreamBandwidthKbps should be LINK_BANDWIDTH_UNSPECIFIED. + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkUpstreamBandwidthKbps()); + nc.setLinkDownstreamBandwidthKbps(512); + nc.setLinkUpstreamBandwidthKbps(128); + assertEquals(512, nc.getLinkDownstreamBandwidthKbps()); + assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(128, nc.getLinkUpstreamBandwidthKbps()); + assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps()); + } + + @Test + public void testSignalStrength() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // The default value of signal strength should be SIGNAL_STRENGTH_UNSPECIFIED. + assertEquals(SIGNAL_STRENGTH_UNSPECIFIED, nc.getSignalStrength()); + nc.setSignalStrength(-80); + assertEquals(-80, nc.getSignalStrength()); + assertNotEquals(-50, nc.getSignalStrength()); + } + + private void assertNoTransport(NetworkCapabilities nc) { + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + assertFalse(nc.hasTransport(i)); + } + } + + // Checks that all transport types from MIN_TRANSPORT to maxTransportType are set and all + // transport types from maxTransportType + 1 to MAX_TRANSPORT are not set when positiveSequence + // is true. If positiveSequence is false, then the check sequence is opposite. + private void checkCurrentTransportTypes(NetworkCapabilities nc, int maxTransportType, + boolean positiveSequence) { + for (int i = MIN_TRANSPORT; i <= maxTransportType; i++) { + if (positiveSequence) { + assertTrue(nc.hasTransport(i)); + } else { + assertFalse(nc.hasTransport(i)); + } + } + for (int i = MAX_TRANSPORT; i > maxTransportType; i--) { + if (positiveSequence) { + assertFalse(nc.hasTransport(i)); + } else { + assertTrue(nc.hasTransport(i)); + } + } + } + + @Test + public void testMultipleTransportTypes() { + final NetworkCapabilities nc = new NetworkCapabilities(); + assertNoTransport(nc); + // Test adding multiple transport types. + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + nc.addTransportType(i); + checkCurrentTransportTypes(nc, i, true /* positiveSequence */); + } + // Test removing multiple transport types. + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + nc.removeTransportType(i); + checkCurrentTransportTypes(nc, i, false /* positiveSequence */); + } + assertNoTransport(nc); + nc.addTransportType(TRANSPORT_WIFI); + assertTrue(nc.hasTransport(TRANSPORT_WIFI)); + assertFalse(nc.hasTransport(TRANSPORT_VPN)); + nc.addTransportType(TRANSPORT_VPN); + assertTrue(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + nc.removeTransportType(TRANSPORT_WIFI); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + nc.removeTransportType(TRANSPORT_VPN); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertFalse(nc.hasTransport(TRANSPORT_VPN)); + assertNoTransport(nc); + } + + @Test + public void testAddAndRemoveTransportType() { + final NetworkCapabilities nc = new NetworkCapabilities(); + try { + nc.addTransportType(-1); + fail("Should not set invalid transport type into addTransportType"); + } catch (IllegalArgumentException expected) { } + try { + nc.removeTransportType(-1); + fail("Should not set invalid transport type into removeTransportType"); + } catch (IllegalArgumentException e) { } + } + + /** + * Test TransportInfo to verify redaction mechanism. + */ + private static class TestTransportInfo implements TransportInfo { + public final boolean locationRedacted; + public final boolean localMacAddressRedacted; + public final boolean settingsRedacted; + + TestTransportInfo() { + locationRedacted = false; + localMacAddressRedacted = false; + settingsRedacted = false; + } + + TestTransportInfo(boolean locationRedacted, + boolean localMacAddressRedacted, + boolean settingsRedacted) { + this.locationRedacted = locationRedacted; + this.localMacAddressRedacted = + localMacAddressRedacted; + this.settingsRedacted = settingsRedacted; + } + + @Override + public TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) { + return new TestTransportInfo( + (redactions & NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION) != 0, + (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0, + (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0 + ); + } + + @Override + public @NetworkCapabilities.RedactionType long getApplicableRedactions() { + return REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS + | REDACT_FOR_NETWORK_SETTINGS; + } + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testBuilder() { + final int ownerUid = 1001; + final int signalStrength = -80; + final int requestUid = 10100; + final int[] administratorUids = {ownerUid, 10001}; + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1); + final TransportInfo transportInfo = new TransportInfo() {}; + final String ssid = "TEST_SSID"; + final String packageName = "com.google.test.networkcapabilities"; + final NetworkCapabilities nc = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .addTransportType(TRANSPORT_CELLULAR) + .removeTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_CBS) + .removeCapability(NET_CAPABILITY_CBS) + .setAdministratorUids(administratorUids) + .setOwnerUid(ownerUid) + .setLinkDownstreamBandwidthKbps(512) + .setLinkUpstreamBandwidthKbps(128) + .setNetworkSpecifier(specifier) + .setTransportInfo(transportInfo) + .setSignalStrength(signalStrength) + .setSsid(ssid) + .setRequestorUid(requestUid) + .setRequestorPackageName(packageName) + .build(); + assertEquals(1, nc.getTransportTypes().length); + assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]); + assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS)); + assertFalse(nc.hasCapability(NET_CAPABILITY_CBS)); + assertTrue(Arrays.equals(administratorUids, nc.getAdministratorUids())); + assertEquals(ownerUid, nc.getOwnerUid()); + assertEquals(512, nc.getLinkDownstreamBandwidthKbps()); + assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(128, nc.getLinkUpstreamBandwidthKbps()); + assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps()); + assertEquals(specifier, nc.getNetworkSpecifier()); + assertEquals(transportInfo, nc.getTransportInfo()); + assertEquals(signalStrength, nc.getSignalStrength()); + assertNotEquals(-50, nc.getSignalStrength()); + assertEquals(ssid, nc.getSsid()); + assertEquals(requestUid, nc.getRequestorUid()); + assertEquals(packageName, nc.getRequestorPackageName()); + // Cannot assign null into NetworkCapabilities.Builder + try { + final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null); + fail("Should not set null into NetworkCapabilities.Builder"); + } catch (NullPointerException expected) { } + assertEquals(nc, new NetworkCapabilities.Builder(nc).build()); + + if (isAtLeastS()) { + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setSubscriptionIds(Set.of(TEST_SUBID1)).build(); + assertEquals(Set.of(TEST_SUBID1), nc2.getSubscriptionIds()); + } + } +} diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt new file mode 100644 index 0000000000..340e6f9631 --- /dev/null +++ b/tests/common/java/android/net/NetworkProviderTest.kt @@ -0,0 +1,201 @@ +/* + * 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 android.net + +import android.app.Instrumentation +import android.content.Context +import android.net.NetworkCapabilities.TRANSPORT_TEST +import android.net.NetworkProviderTest.TestNetworkCallback.CallbackEntry.OnUnavailable +import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequestWithdrawn +import android.net.NetworkProviderTest.TestNetworkProvider.CallbackEntry.OnNetworkRequested +import android.os.Build +import android.os.HandlerThread +import android.os.Looper +import androidx.test.InstrumentationRegistry +import com.android.net.module.util.ArrayTrackRecord +import com.android.testutils.CompatUtil +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.isDevSdkInRange +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.verifyNoMoreInteractions +import java.util.UUID +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +private const val DEFAULT_TIMEOUT_MS = 5000L +private val instrumentation: Instrumentation + get() = InstrumentationRegistry.getInstrumentation() +private val context: Context get() = InstrumentationRegistry.getContext() +private val PROVIDER_NAME = "NetworkProviderTest" + +@RunWith(DevSdkIgnoreRunner::class) +@IgnoreUpTo(Build.VERSION_CODES.Q) +class NetworkProviderTest { + private val mCm = context.getSystemService(ConnectivityManager::class.java) + private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread") + + @Before + fun setUp() { + instrumentation.getUiAutomation().adoptShellPermissionIdentity() + mHandlerThread.start() + } + + @After + fun tearDown() { + mHandlerThread.quitSafely() + instrumentation.getUiAutomation().dropShellPermissionIdentity() + } + + private class TestNetworkProvider(context: Context, looper: Looper) : + NetworkProvider(context, looper, PROVIDER_NAME) { + private val seenEvents = ArrayTrackRecord().newReadHead() + + sealed class CallbackEntry { + data class OnNetworkRequested( + val request: NetworkRequest, + val score: Int, + val id: Int + ) : CallbackEntry() + data class OnNetworkRequestWithdrawn(val request: NetworkRequest) : CallbackEntry() + } + + override fun onNetworkRequested(request: NetworkRequest, score: Int, id: Int) { + seenEvents.add(OnNetworkRequested(request, score, id)) + } + + override fun onNetworkRequestWithdrawn(request: NetworkRequest) { + seenEvents.add(OnNetworkRequestWithdrawn(request)) + } + + inline fun expectCallback( + crossinline predicate: (T) -> Boolean + ) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) } + } + + private fun createNetworkProvider(ctx: Context = context): TestNetworkProvider { + return TestNetworkProvider(ctx, mHandlerThread.looper) + } + + @Test + fun testOnNetworkRequested() { + val provider = createNetworkProvider() + assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + mCm.registerNetworkProvider(provider) + assertNotEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + + val specifier = CompatUtil.makeTestNetworkSpecifier( + UUID.randomUUID().toString()) + val nr: NetworkRequest = NetworkRequest.Builder() + .addTransportType(TRANSPORT_TEST) + .setNetworkSpecifier(specifier) + .build() + val cb = ConnectivityManager.NetworkCallback() + mCm.requestNetwork(nr, cb) + provider.expectCallback() { callback -> + callback.request.getNetworkSpecifier() == specifier && + callback.request.hasTransport(TRANSPORT_TEST) + } + + val initialScore = 40 + val updatedScore = 60 + 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) + setNetworkSpecifier(specifier) + } + val lp = LinkProperties() + val config = NetworkAgentConfig.Builder().build() + val agent = object : NetworkAgent(context, mHandlerThread.looper, "TestAgent", nc, lp, + initialScore, config, provider) {} + + provider.expectCallback() { callback -> + callback.request.getNetworkSpecifier() == specifier && + callback.score == initialScore && + callback.id == agent.providerId + } + + agent.sendNetworkScore(updatedScore) + provider.expectCallback() { callback -> + callback.request.getNetworkSpecifier() == specifier && + callback.score == updatedScore && + callback.id == agent.providerId + } + + mCm.unregisterNetworkCallback(cb) + provider.expectCallback() { callback -> + callback.request.getNetworkSpecifier() == specifier && + callback.request.hasTransport(TRANSPORT_TEST) + } + mCm.unregisterNetworkProvider(provider) + // Provider id should be ID_NONE after unregister network provider + assertEquals(provider.getProviderId(), NetworkProvider.ID_NONE) + // unregisterNetworkProvider should not crash even if it's called on an + // already unregistered provider. + mCm.unregisterNetworkProvider(provider) + } + + private class TestNetworkCallback : ConnectivityManager.NetworkCallback() { + private val seenEvents = ArrayTrackRecord().newReadHead() + sealed class CallbackEntry { + object OnUnavailable : CallbackEntry() + } + + override fun onUnavailable() { + seenEvents.add(OnUnavailable) + } + + inline fun expectCallback( + crossinline predicate: (T) -> Boolean + ) = seenEvents.poll(DEFAULT_TIMEOUT_MS) { it is T && predicate(it) } + } + + @Test + fun testDeclareNetworkRequestUnfulfillable() { + val mockContext = mock(Context::class.java) + doReturn(mCm).`when`(mockContext).getSystemService(Context.CONNECTIVITY_SERVICE) + val provider = createNetworkProvider(mockContext) + // ConnectivityManager not required at creation time after R + if (!isDevSdkInRange(0, Build.VERSION_CODES.R)) { + verifyNoMoreInteractions(mockContext) + } + + mCm.registerNetworkProvider(provider) + + val specifier = CompatUtil.makeTestNetworkSpecifier( + UUID.randomUUID().toString()) + val nr: NetworkRequest = NetworkRequest.Builder() + .addTransportType(TRANSPORT_TEST) + .setNetworkSpecifier(specifier) + .build() + + val cb = TestNetworkCallback() + mCm.requestNetwork(nr, cb) + provider.declareNetworkRequestUnfulfillable(nr) + cb.expectCallback() { nr.getNetworkSpecifier() == specifier } + mCm.unregisterNetworkProvider(provider) + } +} \ No newline at end of file diff --git a/tests/common/java/android/net/NetworkSpecifierTest.kt b/tests/common/java/android/net/NetworkSpecifierTest.kt new file mode 100644 index 0000000000..f3409f5359 --- /dev/null +++ b/tests/common/java/android/net/NetworkSpecifierTest.kt @@ -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 android.net + +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import kotlin.test.assertTrue +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(DevSdkIgnoreRunner::class) +@IgnoreUpTo(Build.VERSION_CODES.Q) +class NetworkSpecifierTest { + private class TestNetworkSpecifier( + val intData: Int = 123, + val stringData: String = "init" + ) : NetworkSpecifier() { + override fun canBeSatisfiedBy(other: NetworkSpecifier?): Boolean = + other != null && + other is TestNetworkSpecifier && + other.intData >= intData && + stringData.equals(other.stringData) + + override fun redact(): NetworkSpecifier = TestNetworkSpecifier(intData, "redact") + } + + @Test + fun testRedact() { + val ns: TestNetworkSpecifier = TestNetworkSpecifier() + val redactNs = ns.redact() + assertTrue(redactNs is TestNetworkSpecifier) + assertEquals(ns.intData, redactNs.intData) + assertNotEquals(ns.stringData, redactNs.stringData) + assertTrue("redact".equals(redactNs.stringData)) + } + + @Test + fun testcanBeSatisfiedBy() { + val target: TestNetworkSpecifier = TestNetworkSpecifier() + assertFalse(target.canBeSatisfiedBy(null)) + assertTrue(target.canBeSatisfiedBy(TestNetworkSpecifier())) + val otherNs = TelephonyNetworkSpecifier.Builder().setSubscriptionId(123).build() + assertFalse(target.canBeSatisfiedBy(otherNs)) + assertTrue(target.canBeSatisfiedBy(TestNetworkSpecifier(intData = 999))) + assertFalse(target.canBeSatisfiedBy(TestNetworkSpecifier(intData = 1))) + assertFalse(target.canBeSatisfiedBy(TestNetworkSpecifier(stringData = "diff"))) + } +} \ No newline at end of file diff --git a/tests/common/java/android/net/NetworkStackTest.java b/tests/common/java/android/net/NetworkStackTest.java new file mode 100644 index 0000000000..f8f9c72374 --- /dev/null +++ b/tests/common/java/android/net/NetworkStackTest.java @@ -0,0 +1,51 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; + +import android.os.Build; +import android.os.IBinder; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class NetworkStackTest { + @Rule + public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); + + @Mock private IBinder mConnectorBinder; + + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testGetService() { + NetworkStack.setServiceForTest(mConnectorBinder); + assertEquals(NetworkStack.getService(), mConnectorBinder); + } +} diff --git a/tests/common/java/android/net/NetworkStateSnapshotTest.kt b/tests/common/java/android/net/NetworkStateSnapshotTest.kt new file mode 100644 index 0000000000..0ca4d9551f --- /dev/null +++ b/tests/common/java/android/net/NetworkStateSnapshotTest.kt @@ -0,0 +1,73 @@ +/* + * 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 android.net + +import android.net.ConnectivityManager.TYPE_NONE +import android.net.ConnectivityManager.TYPE_WIFI +import android.net.InetAddresses.parseNumericAddress +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.assertParcelSane +import org.junit.Test +import org.junit.runner.RunWith +import java.net.Inet4Address +import java.net.Inet6Address + +private const val TEST_IMSI = "imsi1" +private const val TEST_SSID = "SSID1" +private const val TEST_NETID = 123 + +private val TEST_IPV4_GATEWAY = parseNumericAddress("192.168.222.3") as Inet4Address +private val TEST_IPV6_GATEWAY = parseNumericAddress("2001:db8::1") as Inet6Address +private val TEST_IPV4_LINKADDR = LinkAddress("192.168.222.123/24") +private val TEST_IPV6_LINKADDR = LinkAddress("2001:db8::123/64") +private val TEST_IFACE = "fake0" +private val TEST_LINK_PROPERTIES = LinkProperties().apply { + interfaceName = TEST_IFACE + addLinkAddress(TEST_IPV4_LINKADDR) + addLinkAddress(TEST_IPV6_LINKADDR) + + // Add default routes + addRoute(RouteInfo(IpPrefix(parseNumericAddress("0.0.0.0"), 0), TEST_IPV4_GATEWAY)) + addRoute(RouteInfo(IpPrefix(parseNumericAddress("::"), 0), TEST_IPV6_GATEWAY)) +} + +private val TEST_CAPABILITIES = NetworkCapabilities().apply { + addTransportType(TRANSPORT_WIFI) + setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false) + setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true) + setSSID(TEST_SSID) +} + +@SmallTest +@RunWith(DevSdkIgnoreRunner::class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +class NetworkStateSnapshotTest { + + @Test + fun testParcelUnparcel() { + val emptySnapshot = NetworkStateSnapshot(Network(TEST_NETID), NetworkCapabilities(), + LinkProperties(), null, TYPE_NONE) + val snapshot = NetworkStateSnapshot( + Network(TEST_NETID), TEST_CAPABILITIES, TEST_LINK_PROPERTIES, TEST_IMSI, TYPE_WIFI) + assertParcelSane(emptySnapshot, 5) + assertParcelSane(snapshot, 5) + } +} diff --git a/tests/common/java/android/net/NetworkTest.java b/tests/common/java/android/net/NetworkTest.java new file mode 100644 index 0000000000..11d44b86bc --- /dev/null +++ b/tests/common/java/android/net/NetworkTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.platform.test.annotations.AppModeFull; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.net.DatagramSocket; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.SocketException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkTest { + final Network mNetwork = new Network(99); + + @Test + public void testBindSocketOfInvalidFdThrows() throws Exception { + + final FileDescriptor fd = new FileDescriptor(); + assertFalse(fd.valid()); + + try { + mNetwork.bindSocket(fd); + fail("SocketException not thrown"); + } catch (SocketException expected) {} + } + + @Test + public void testBindSocketOfNonSocketFdThrows() throws Exception { + final File devNull = new File("/dev/null"); + assertTrue(devNull.canRead()); + + final FileInputStream fis = new FileInputStream(devNull); + assertTrue(null != fis.getFD()); + assertTrue(fis.getFD().valid()); + + try { + mNetwork.bindSocket(fis.getFD()); + fail("SocketException not thrown"); + } catch (SocketException expected) {} + } + + @Test + @AppModeFull(reason = "Socket cannot bind in instant app mode") + public void testBindSocketOfConnectedDatagramSocketThrows() throws Exception { + final DatagramSocket mDgramSocket = new DatagramSocket(0, (InetAddress) Inet6Address.ANY); + mDgramSocket.connect((InetAddress) Inet6Address.LOOPBACK, 53); + assertTrue(mDgramSocket.isConnected()); + + try { + mNetwork.bindSocket(mDgramSocket); + fail("SocketException not thrown"); + } catch (SocketException expected) {} + } + + @Test + public void testBindSocketOfLocalSocketThrows() throws Exception { + final LocalSocket mLocalClient = new LocalSocket(); + mLocalClient.bind(new LocalSocketAddress("testClient")); + assertTrue(mLocalClient.getFileDescriptor().valid()); + + try { + mNetwork.bindSocket(mLocalClient.getFileDescriptor()); + fail("SocketException not thrown"); + } catch (SocketException expected) {} + + final LocalServerSocket mLocalServer = new LocalServerSocket("testServer"); + mLocalClient.connect(mLocalServer.getLocalSocketAddress()); + assertTrue(mLocalClient.isConnected()); + + try { + mNetwork.bindSocket(mLocalClient.getFileDescriptor()); + fail("SocketException not thrown"); + } catch (SocketException expected) {} + } + + @Test + public void testZeroIsObviousForDebugging() { + Network zero = new Network(0); + assertEquals(0, zero.hashCode()); + assertEquals(0, zero.getNetworkHandle()); + assertEquals("0", zero.toString()); + } + + @Test + public void testGetNetworkHandle() { + Network one = new Network(1); + Network two = new Network(2); + Network three = new Network(3); + + // None of the hashcodes are zero. + assertNotEquals(0, one.hashCode()); + assertNotEquals(0, two.hashCode()); + assertNotEquals(0, three.hashCode()); + + // All the hashcodes are distinct. + assertNotEquals(one.hashCode(), two.hashCode()); + assertNotEquals(one.hashCode(), three.hashCode()); + assertNotEquals(two.hashCode(), three.hashCode()); + + // None of the handles are zero. + assertNotEquals(0, one.getNetworkHandle()); + assertNotEquals(0, two.getNetworkHandle()); + assertNotEquals(0, three.getNetworkHandle()); + + // All the handles are distinct. + assertNotEquals(one.getNetworkHandle(), two.getNetworkHandle()); + assertNotEquals(one.getNetworkHandle(), three.getNetworkHandle()); + assertNotEquals(two.getNetworkHandle(), three.getNetworkHandle()); + + // The handles are not equal to the hashcodes. + assertNotEquals(one.hashCode(), one.getNetworkHandle()); + assertNotEquals(two.hashCode(), two.getNetworkHandle()); + assertNotEquals(three.hashCode(), three.getNetworkHandle()); + + // Adjust as necessary to test an implementation's specific constants. + // When running with runtest, "adb logcat -s TestRunner" can be useful. + assertEquals(7700664333L, one.getNetworkHandle()); + assertEquals(11995631629L, two.getNetworkHandle()); + assertEquals(16290598925L, three.getNetworkHandle()); + } + + @Test + public void testGetPrivateDnsBypassingCopy() { + final Network copy = mNetwork.getPrivateDnsBypassingCopy(); + assertEquals(mNetwork.netId, copy.netId); + assertNotEquals(copy.netId, copy.getNetIdForResolv()); + assertNotEquals(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv()); + } +} diff --git a/tests/common/java/android/net/OemNetworkPreferencesTest.java b/tests/common/java/android/net/OemNetworkPreferencesTest.java new file mode 100644 index 0000000000..fd29a9539d --- /dev/null +++ b/tests/common/java/android/net/OemNetworkPreferencesTest.java @@ -0,0 +1,152 @@ +/* + * 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 android.net; + +import static com.android.testutils.MiscAsserts.assertThrows; +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Build; + +import androidx.test.filters.SmallTest; + +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Map; + +@IgnoreUpTo(Build.VERSION_CODES.R) +@RunWith(DevSdkIgnoreRunner.class) +@SmallTest +public class OemNetworkPreferencesTest { + + private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; + private static final String TEST_PACKAGE = "com.google.apps.contacts"; + + private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder(); + + @Test + public void testBuilderAddNetworkPreferenceRequiresNonNullPackageName() { + assertThrows(NullPointerException.class, + () -> mBuilder.addNetworkPreference(null, TEST_PREF)); + } + + @Test + public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() { + assertThrows(NullPointerException.class, + () -> mBuilder.clearNetworkPreference(null)); + } + + @Test + public void testGetNetworkPreferenceReturnsCorrectValue() { + final int expectedNumberOfMappings = 1; + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + + final Map networkPreferences = + mBuilder.build().getNetworkPreferences(); + + assertEquals(expectedNumberOfMappings, networkPreferences.size()); + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + } + + @Test + public void testGetNetworkPreferenceReturnsUnmodifiableValue() { + final String newPackage = "new.com.google.apps.contacts"; + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + + final Map networkPreferences = + mBuilder.build().getNetworkPreferences(); + + assertThrows(UnsupportedOperationException.class, + () -> networkPreferences.put(newPackage, TEST_PREF)); + + assertThrows(UnsupportedOperationException.class, + () -> networkPreferences.remove(TEST_PACKAGE)); + + } + + @Test + public void testToStringReturnsCorrectValue() { + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + + final String networkPreferencesString = mBuilder.build().getNetworkPreferences().toString(); + + assertTrue(networkPreferencesString.contains(Integer.toString(TEST_PREF))); + assertTrue(networkPreferencesString.contains(TEST_PACKAGE)); + } + + @Test + public void testOemNetworkPreferencesParcelable() { + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + + final OemNetworkPreferences prefs = mBuilder.build(); + + assertParcelSane(prefs, 1 /* fieldCount */); + } + + @Test + public void testAddNetworkPreferenceOverwritesPriorPreference() { + final int newPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + Map networkPreferences = + mBuilder.build().getNetworkPreferences(); + + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), TEST_PREF); + + mBuilder.addNetworkPreference(TEST_PACKAGE, newPref); + networkPreferences = mBuilder.build().getNetworkPreferences(); + + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), newPref); + } + + @Test + public void testRemoveNetworkPreferenceRemovesValue() { + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + Map networkPreferences = + mBuilder.build().getNetworkPreferences(); + + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + + mBuilder.clearNetworkPreference(TEST_PACKAGE); + networkPreferences = mBuilder.build().getNetworkPreferences(); + + assertFalse(networkPreferences.containsKey(TEST_PACKAGE)); + } + + @Test + public void testConstructorByOemNetworkPreferencesSetsValue() { + mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF); + OemNetworkPreferences networkPreference = mBuilder.build(); + + final Map networkPreferences = + new OemNetworkPreferences + .Builder(networkPreference) + .build() + .getNetworkPreferences(); + + assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); + assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), TEST_PREF); + } +} diff --git a/tests/common/java/android/net/RouteInfoTest.java b/tests/common/java/android/net/RouteInfoTest.java new file mode 100644 index 0000000000..71689f9197 --- /dev/null +++ b/tests/common/java/android/net/RouteInfoTest.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2010 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; + +import static android.net.RouteInfo.RTN_UNREACHABLE; + +import static com.android.testutils.MiscAsserts.assertEqualBothWays; +import static com.android.testutils.MiscAsserts.assertFieldCountEquals; +import static com.android.testutils.MiscAsserts.assertNotEqualEitherWay; +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Build; + +import androidx.core.os.BuildCompat; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RouteInfoTest { + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + + private static final int INVALID_ROUTE_TYPE = -1; + + private InetAddress Address(String addr) { + return InetAddresses.parseNumericAddress(addr); + } + + private IpPrefix Prefix(String prefix) { + return new IpPrefix(prefix); + } + + private static boolean isAtLeastR() { + // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R) + return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR(); + } + + @Test + public void testConstructor() { + RouteInfo r; + // Invalid input. + try { + r = new RouteInfo((IpPrefix) null, null, "rmnet0"); + fail("Expected RuntimeException: destination and gateway null"); + } catch (RuntimeException e) { } + + try { + r = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("2001:db8::1"), "rmnet0", + INVALID_ROUTE_TYPE); + fail("Invalid route type should cause exception"); + } catch (IllegalArgumentException e) { } + + try { + r = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("192.0.2.1"), "rmnet0", + RTN_UNREACHABLE); + fail("Address family mismatch should cause exception"); + } catch (IllegalArgumentException e) { } + + try { + r = new RouteInfo(Prefix("0.0.0.0/0"), Address("2001:db8::1"), "rmnet0", + RTN_UNREACHABLE); + fail("Address family mismatch should cause exception"); + } catch (IllegalArgumentException e) { } + + // Null destination is default route. + r = new RouteInfo((IpPrefix) null, Address("2001:db8::1"), null); + assertEquals(Prefix("::/0"), r.getDestination()); + assertEquals(Address("2001:db8::1"), r.getGateway()); + assertNull(r.getInterface()); + + r = new RouteInfo((IpPrefix) null, Address("192.0.2.1"), "wlan0"); + assertEquals(Prefix("0.0.0.0/0"), r.getDestination()); + assertEquals(Address("192.0.2.1"), r.getGateway()); + assertEquals("wlan0", r.getInterface()); + + // Null gateway sets gateway to unspecified address (why?). + r = new RouteInfo(Prefix("2001:db8:beef:cafe::/48"), null, "lo"); + assertEquals(Prefix("2001:db8:beef::/48"), r.getDestination()); + assertEquals(Address("::"), r.getGateway()); + assertEquals("lo", r.getInterface()); + + r = new RouteInfo(Prefix("192.0.2.5/24"), null); + assertEquals(Prefix("192.0.2.0/24"), r.getDestination()); + assertEquals(Address("0.0.0.0"), r.getGateway()); + assertNull(r.getInterface()); + } + + @Test + public void testMatches() { + class PatchedRouteInfo { + private final RouteInfo mRouteInfo; + + public PatchedRouteInfo(IpPrefix destination, InetAddress gateway, String iface) { + mRouteInfo = new RouteInfo(destination, gateway, iface); + } + + public boolean matches(InetAddress destination) { + return mRouteInfo.matches(destination); + } + } + + PatchedRouteInfo r; + + r = new PatchedRouteInfo(Prefix("2001:db8:f00::ace:d00d/127"), null, "rmnet0"); + assertTrue(r.matches(Address("2001:db8:f00::ace:d00c"))); + assertTrue(r.matches(Address("2001:db8:f00::ace:d00d"))); + assertFalse(r.matches(Address("2001:db8:f00::ace:d00e"))); + assertFalse(r.matches(Address("2001:db8:f00::bad:d00d"))); + assertFalse(r.matches(Address("2001:4868:4860::8888"))); + assertFalse(r.matches(Address("8.8.8.8"))); + + r = new PatchedRouteInfo(Prefix("192.0.2.0/23"), null, "wlan0"); + assertTrue(r.matches(Address("192.0.2.43"))); + assertTrue(r.matches(Address("192.0.3.21"))); + assertFalse(r.matches(Address("192.0.0.21"))); + assertFalse(r.matches(Address("8.8.8.8"))); + + PatchedRouteInfo ipv6Default = new PatchedRouteInfo(Prefix("::/0"), null, "rmnet0"); + assertTrue(ipv6Default.matches(Address("2001:db8::f00"))); + assertFalse(ipv6Default.matches(Address("192.0.2.1"))); + + PatchedRouteInfo ipv4Default = new PatchedRouteInfo(Prefix("0.0.0.0/0"), null, "rmnet0"); + assertTrue(ipv4Default.matches(Address("255.255.255.255"))); + assertTrue(ipv4Default.matches(Address("192.0.2.1"))); + assertFalse(ipv4Default.matches(Address("2001:db8::f00"))); + } + + @Test + public void testEquals() { + // IPv4 + RouteInfo r1 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0"); + RouteInfo r2 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0"); + assertEqualBothWays(r1, r2); + + RouteInfo r3 = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("2001:db8::1"), "wlan0"); + RouteInfo r4 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::2"), "wlan0"); + RouteInfo r5 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "rmnet0"); + assertNotEqualEitherWay(r1, r3); + assertNotEqualEitherWay(r1, r4); + assertNotEqualEitherWay(r1, r5); + + // IPv6 + r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0"); + r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0"); + assertEqualBothWays(r1, r2); + + r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0"); + r4 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.2"), "wlan0"); + r5 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "rmnet0"); + assertNotEqualEitherWay(r1, r3); + assertNotEqualEitherWay(r1, r4); + assertNotEqualEitherWay(r1, r5); + + // Interfaces (but not destinations or gateways) can be null. + r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null); + r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null); + r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0"); + assertEqualBothWays(r1, r2); + assertNotEqualEitherWay(r1, r3); + } + + @Test + public void testHostAndDefaultRoutes() { + RouteInfo r; + + r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0"); + assertFalse(r.isHostRoute()); + assertTrue(r.isDefaultRoute()); + assertTrue(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0"); + assertFalse(r.isHostRoute()); + assertTrue(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertTrue(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0"); + assertFalse(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0"); + assertFalse(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0"); + assertTrue(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0"); + assertTrue(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0"); + assertTrue(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0"); + assertTrue(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0"); + assertTrue(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0"); + assertTrue(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0"); + assertTrue(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE); + assertFalse(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertTrue(r.isIPv4UnreachableDefault()); + assertFalse(r.isIPv6UnreachableDefault()); + } + + r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE); + assertFalse(r.isHostRoute()); + assertFalse(r.isDefaultRoute()); + assertFalse(r.isIPv4Default()); + assertFalse(r.isIPv6Default()); + if (isAtLeastR()) { + assertFalse(r.isIPv4UnreachableDefault()); + assertTrue(r.isIPv6UnreachableDefault()); + } + } + + @Test + public void testTruncation() { + LinkAddress l; + RouteInfo r; + + l = new LinkAddress("192.0.2.5/30"); + r = new RouteInfo(l, Address("192.0.2.1"), "wlan0"); + assertEquals("192.0.2.4", r.getDestination().getAddress().getHostAddress()); + + l = new LinkAddress("2001:db8:1:f::5/63"); + r = new RouteInfo(l, Address("2001:db8:5::1"), "wlan0"); + assertEquals("2001:db8:1:e::", r.getDestination().getAddress().getHostAddress()); + } + + // Make sure that creating routes to multicast addresses doesn't throw an exception. Even though + // there's nothing we can do with them, we don't want to crash if, e.g., someone calls + // requestRouteToHostAddress("230.0.0.0", MOBILE_HIPRI); + @Test + public void testMulticastRoute() { + RouteInfo r; + r = new RouteInfo(Prefix("230.0.0.0/32"), Address("192.0.2.1"), "wlan0"); + r = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::1"), "wlan0"); + // No exceptions? Good. + } + + @Test + public void testParceling() { + RouteInfo r; + r = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), null); + assertParcelingIsLossless(r); + r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0"); + assertParcelingIsLossless(r); + r = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", RTN_UNREACHABLE); + assertParcelingIsLossless(r); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testMtuParceling() { + final RouteInfo r = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::"), "testiface", + RTN_UNREACHABLE, 1450 /* mtu */); + assertParcelingIsLossless(r); + } + + @Test @IgnoreAfter(Build.VERSION_CODES.Q) + public void testFieldCount_Q() { + assertFieldCountEquals(6, RouteInfo.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testFieldCount() { + // Make sure any new field is covered by the above parceling tests when changing this number + assertFieldCountEquals(7, RouteInfo.class); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testMtu() { + RouteInfo r; + r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0", + RouteInfo.RTN_UNICAST, 1500); + assertEquals(1500, r.getMtu()); + + r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0"); + assertEquals(0, r.getMtu()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testRouteKey() { + RouteInfo.RouteKey k1, k2; + // Only prefix, null gateway and null interface + k1 = new RouteInfo(Prefix("2001:db8::/128"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("2001:db8::/128"), null).getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // With prefix, gateway and interface. Type and MTU does not affect RouteKey equality + k1 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", + RTN_UNREACHABLE, 1450).getRouteKey(); + k2 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0", + RouteInfo.RTN_UNICAST, 1400).getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // Different scope IDs are ignored by the kernel, so we consider them equal here too. + k1 = new RouteInfo(Prefix("2001:db8::/64"), Address("fe80::1%1"), "wlan0").getRouteKey(); + k2 = new RouteInfo(Prefix("2001:db8::/64"), Address("fe80::1%2"), "wlan0").getRouteKey(); + assertEquals(k1, k2); + assertEquals(k1.hashCode(), k2.hashCode()); + + // Different prefix + k1 = new RouteInfo(Prefix("192.0.2.0/24"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("192.0.3.0/24"), null).getRouteKey(); + assertNotEquals(k1, k2); + + // Different gateway + k1 = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::1"), null).getRouteKey(); + k2 = new RouteInfo(Prefix("ff02::1/128"), Address("2001:db8::2"), null).getRouteKey(); + assertNotEquals(k1, k2); + + // Different interface + k1 = new RouteInfo(Prefix("ff02::1/128"), null, "tun0").getRouteKey(); + k2 = new RouteInfo(Prefix("ff02::1/128"), null, "tun1").getRouteKey(); + assertNotEquals(k1, k2); + } +} diff --git a/tests/common/java/android/net/StaticIpConfigurationTest.java b/tests/common/java/android/net/StaticIpConfigurationTest.java new file mode 100644 index 0000000000..b5f23bf19a --- /dev/null +++ b/tests/common/java/android/net/StaticIpConfigurationTest.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2014 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.os.Parcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StaticIpConfigurationTest { + + private static final String ADDRSTR = "192.0.2.2/25"; + private static final LinkAddress ADDR = new LinkAddress(ADDRSTR); + private static final InetAddress GATEWAY = IpAddress("192.0.2.1"); + private static final InetAddress OFFLINKGATEWAY = IpAddress("192.0.2.129"); + private static final InetAddress DNS1 = IpAddress("8.8.8.8"); + private static final InetAddress DNS2 = IpAddress("8.8.4.4"); + private static final InetAddress DNS3 = IpAddress("4.2.2.2"); + private static final String IFACE = "eth0"; + private static final String FAKE_DOMAINS = "google.com"; + + private static InetAddress IpAddress(String addr) { + return InetAddress.parseNumericAddress(addr); + } + + private void checkEmpty(StaticIpConfiguration s) { + assertNull(s.ipAddress); + assertNull(s.gateway); + assertNull(s.domains); + assertEquals(0, s.dnsServers.size()); + } + + private StaticIpConfiguration makeTestObject() { + StaticIpConfiguration s = new StaticIpConfiguration(); + s.ipAddress = ADDR; + s.gateway = GATEWAY; + s.dnsServers.add(DNS1); + s.dnsServers.add(DNS2); + s.dnsServers.add(DNS3); + s.domains = FAKE_DOMAINS; + return s; + } + + @Test + public void testConstructor() { + StaticIpConfiguration s = new StaticIpConfiguration(); + checkEmpty(s); + } + + @Test + public void testCopyAndClear() { + StaticIpConfiguration empty = new StaticIpConfiguration((StaticIpConfiguration) null); + checkEmpty(empty); + + StaticIpConfiguration s1 = makeTestObject(); + StaticIpConfiguration s2 = new StaticIpConfiguration(s1); + assertEquals(s1, s2); + s2.clear(); + assertEquals(empty, s2); + } + + @Test + public void testHashCodeAndEquals() { + HashSet hashCodes = new HashSet(); + hashCodes.add(0); + + StaticIpConfiguration s = new StaticIpConfiguration(); + // Check that this hash code is nonzero and different from all the ones seen so far. + assertTrue(hashCodes.add(s.hashCode())); + + s.ipAddress = ADDR; + assertTrue(hashCodes.add(s.hashCode())); + + s.gateway = GATEWAY; + assertTrue(hashCodes.add(s.hashCode())); + + s.dnsServers.add(DNS1); + assertTrue(hashCodes.add(s.hashCode())); + + s.dnsServers.add(DNS2); + assertTrue(hashCodes.add(s.hashCode())); + + s.dnsServers.add(DNS3); + assertTrue(hashCodes.add(s.hashCode())); + + s.domains = "example.com"; + assertTrue(hashCodes.add(s.hashCode())); + + assertFalse(s.equals(null)); + assertEquals(s, s); + + StaticIpConfiguration s2 = new StaticIpConfiguration(s); + assertEquals(s, s2); + + s.ipAddress = new LinkAddress(DNS1, 32); + assertNotEquals(s, s2); + + s2 = new StaticIpConfiguration(s); + s.domains = "foo"; + assertNotEquals(s, s2); + + s2 = new StaticIpConfiguration(s); + s.gateway = DNS2; + assertNotEquals(s, s2); + + s2 = new StaticIpConfiguration(s); + s.dnsServers.add(DNS3); + assertNotEquals(s, s2); + } + + @Test + public void testToLinkProperties() { + LinkProperties expected = new LinkProperties(); + expected.setInterfaceName(IFACE); + + StaticIpConfiguration s = new StaticIpConfiguration(); + assertEquals(expected, s.toLinkProperties(IFACE)); + + final RouteInfo connectedRoute = new RouteInfo(new IpPrefix(ADDRSTR), null, IFACE); + s.ipAddress = ADDR; + expected.addLinkAddress(ADDR); + expected.addRoute(connectedRoute); + assertEquals(expected, s.toLinkProperties(IFACE)); + + s.gateway = GATEWAY; + RouteInfo defaultRoute = new RouteInfo(new IpPrefix("0.0.0.0/0"), GATEWAY, IFACE); + expected.addRoute(defaultRoute); + assertEquals(expected, s.toLinkProperties(IFACE)); + + s.gateway = OFFLINKGATEWAY; + expected.removeRoute(defaultRoute); + defaultRoute = new RouteInfo(new IpPrefix("0.0.0.0/0"), OFFLINKGATEWAY, IFACE); + expected.addRoute(defaultRoute); + + RouteInfo gatewayRoute = new RouteInfo(new IpPrefix("192.0.2.129/32"), null, IFACE); + expected.addRoute(gatewayRoute); + assertEquals(expected, s.toLinkProperties(IFACE)); + + s.dnsServers.add(DNS1); + expected.addDnsServer(DNS1); + assertEquals(expected, s.toLinkProperties(IFACE)); + + s.dnsServers.add(DNS2); + s.dnsServers.add(DNS3); + expected.addDnsServer(DNS2); + expected.addDnsServer(DNS3); + assertEquals(expected, s.toLinkProperties(IFACE)); + + s.domains = FAKE_DOMAINS; + expected.setDomains(FAKE_DOMAINS); + assertEquals(expected, s.toLinkProperties(IFACE)); + + s.gateway = null; + expected.removeRoute(defaultRoute); + expected.removeRoute(gatewayRoute); + assertEquals(expected, s.toLinkProperties(IFACE)); + + // Without knowing the IP address, we don't have a directly-connected route, so we can't + // tell if the gateway is off-link or not and we don't add a host route. This isn't a real + // configuration, but we should at least not crash. + s.gateway = OFFLINKGATEWAY; + s.ipAddress = null; + expected.removeLinkAddress(ADDR); + expected.removeRoute(connectedRoute); + expected.addRoute(defaultRoute); + assertEquals(expected, s.toLinkProperties(IFACE)); + } + + private StaticIpConfiguration passThroughParcel(StaticIpConfiguration s) { + Parcel p = Parcel.obtain(); + StaticIpConfiguration s2 = null; + try { + s.writeToParcel(p, 0); + p.setDataPosition(0); + s2 = StaticIpConfiguration.readFromParcel(p); + } finally { + p.recycle(); + } + assertNotNull(s2); + return s2; + } + + @Test + public void testParceling() { + StaticIpConfiguration s = makeTestObject(); + StaticIpConfiguration s2 = passThroughParcel(s); + assertEquals(s, s2); + } + + @Test + public void testBuilder() { + final ArrayList dnsServers = new ArrayList<>(); + dnsServers.add(DNS1); + + final StaticIpConfiguration s = new StaticIpConfiguration.Builder() + .setIpAddress(ADDR) + .setGateway(GATEWAY) + .setDomains(FAKE_DOMAINS) + .setDnsServers(dnsServers) + .build(); + + assertEquals(s.ipAddress, s.getIpAddress()); + assertEquals(ADDR, s.getIpAddress()); + assertEquals(s.gateway, s.getGateway()); + assertEquals(GATEWAY, s.getGateway()); + assertEquals(s.domains, s.getDomains()); + assertEquals(FAKE_DOMAINS, s.getDomains()); + assertTrue(s.dnsServers.equals(s.getDnsServers())); + assertEquals(1, s.getDnsServers().size()); + assertEquals(DNS1, s.getDnsServers().get(0)); + } + + @Test + public void testAddDnsServers() { + final StaticIpConfiguration s = new StaticIpConfiguration((StaticIpConfiguration) null); + checkEmpty(s); + + s.addDnsServer(DNS1); + assertEquals(1, s.getDnsServers().size()); + assertEquals(DNS1, s.getDnsServers().get(0)); + + s.addDnsServer(DNS2); + s.addDnsServer(DNS3); + assertEquals(3, s.getDnsServers().size()); + assertEquals(DNS2, s.getDnsServers().get(1)); + assertEquals(DNS3, s.getDnsServers().get(2)); + } + + @Test + public void testGetRoutes() { + final StaticIpConfiguration s = makeTestObject(); + final List routeInfoList = s.getRoutes(IFACE); + + assertEquals(2, routeInfoList.size()); + assertEquals(new RouteInfo(ADDR, (InetAddress) null, IFACE), routeInfoList.get(0)); + assertEquals(new RouteInfo((IpPrefix) null, GATEWAY, IFACE), routeInfoList.get(1)); + } +} diff --git a/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt b/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt new file mode 100644 index 0000000000..7a18bb08fa --- /dev/null +++ b/tests/common/java/android/net/TcpKeepalivePacketDataTest.kt @@ -0,0 +1,106 @@ +/* + * 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 android.net + +import android.net.InetAddresses.parseNumericAddress +import android.os.Build +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.assertFieldCountEquals +import com.android.testutils.assertParcelSane +import org.junit.Test +import org.junit.runner.RunWith +import java.net.InetAddress +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue + +@RunWith(DevSdkIgnoreRunner::class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) // TcpKeepalivePacketData added to SDK in S +class TcpKeepalivePacketDataTest { + private fun makeData( + srcAddress: InetAddress = parseNumericAddress("192.0.2.123"), + srcPort: Int = 1234, + dstAddress: InetAddress = parseNumericAddress("192.0.2.231"), + dstPort: Int = 4321, + data: ByteArray = byteArrayOf(1, 2, 3), + tcpSeq: Int = 135, + tcpAck: Int = 246, + tcpWnd: Int = 1234, + tcpWndScale: Int = 2, + ipTos: Int = 0x12, + ipTtl: Int = 10 + ) = TcpKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, data, tcpSeq, tcpAck, + tcpWnd, tcpWndScale, ipTos, ipTtl) + + @Test + fun testEquals() { + val data1 = makeData() + val data2 = makeData() + assertEquals(data1, data2) + assertEquals(data1.hashCode(), data2.hashCode()) + } + + @Test + fun testNotEquals() { + assertNotEquals(makeData(srcAddress = parseNumericAddress("192.0.2.124")), makeData()) + assertNotEquals(makeData(srcPort = 1235), makeData()) + assertNotEquals(makeData(dstAddress = parseNumericAddress("192.0.2.232")), makeData()) + assertNotEquals(makeData(dstPort = 4322), makeData()) + // .equals does not test .packet, as it should be generated from the other fields + assertNotEquals(makeData(tcpSeq = 136), makeData()) + assertNotEquals(makeData(tcpAck = 247), makeData()) + assertNotEquals(makeData(tcpWnd = 1235), makeData()) + assertNotEquals(makeData(tcpWndScale = 3), makeData()) + assertNotEquals(makeData(ipTos = 0x14), makeData()) + assertNotEquals(makeData(ipTtl = 11), makeData()) + + // Update above assertions if field is added + assertFieldCountEquals(5, KeepalivePacketData::class.java) + assertFieldCountEquals(6, TcpKeepalivePacketData::class.java) + } + + @Test + fun testParcelUnparcel() { + assertParcelSane(makeData(), fieldCount = 6) { a, b -> + // .equals() does not verify .packet + a == b && a.packet contentEquals b.packet + } + } + + @Test + fun testToString() { + val data = makeData() + val str = data.toString() + + assertTrue(str.contains(data.srcAddress.hostAddress)) + assertTrue(str.contains(data.srcPort.toString())) + assertTrue(str.contains(data.dstAddress.hostAddress)) + assertTrue(str.contains(data.dstPort.toString())) + // .packet not included in toString() + assertTrue(str.contains(data.getTcpSeq().toString())) + assertTrue(str.contains(data.getTcpAck().toString())) + assertTrue(str.contains(data.getTcpWindow().toString())) + assertTrue(str.contains(data.getTcpWindowScale().toString())) + assertTrue(str.contains(data.getIpTos().toString())) + assertTrue(str.contains(data.getIpTtl().toString())) + + // Update above assertions if field is added + assertFieldCountEquals(5, KeepalivePacketData::class.java) + assertFieldCountEquals(6, TcpKeepalivePacketData::class.java) + } +} \ No newline at end of file diff --git a/tests/common/java/android/net/UidRangeTest.java b/tests/common/java/android/net/UidRangeTest.java new file mode 100644 index 0000000000..1b1c95431d --- /dev/null +++ b/tests/common/java/android/net/UidRangeTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.os.UserHandle.MIN_SECONDARY_USER_ID; +import static android.os.UserHandle.SYSTEM; +import static android.os.UserHandle.USER_SYSTEM; +import static android.os.UserHandle.getUid; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Build; +import android.os.UserHandle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class UidRangeTest { + + /* + * UidRange is no longer passed to netd. UID ranges between the framework and netd are passed as + * UidRangeParcel objects. + */ + + @Rule + public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); + + @Test + public void testSingleItemUidRangeAllowed() { + new UidRange(123, 123); + new UidRange(0, 0); + new UidRange(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + @Test + public void testNegativeUidsDisallowed() { + try { + new UidRange(-2, 100); + fail("Exception not thrown for negative start UID"); + } catch (IllegalArgumentException expected) { + } + + try { + new UidRange(-200, -100); + fail("Exception not thrown for negative stop UID"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testStopLessThanStartDisallowed() { + final int x = 4195000; + try { + new UidRange(x, x - 1); + fail("Exception not thrown for negative-length UID range"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testGetStartAndEndUser() throws Exception { + final UidRange uidRangeOfPrimaryUser = new UidRange( + getUid(USER_SYSTEM, 10000), getUid(USER_SYSTEM, 10100)); + final UidRange uidRangeOfSecondaryUser = new UidRange( + getUid(MIN_SECONDARY_USER_ID, 10000), getUid(MIN_SECONDARY_USER_ID, 10100)); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getStartUser()); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getEndUser()); + assertEquals(MIN_SECONDARY_USER_ID, uidRangeOfSecondaryUser.getStartUser()); + assertEquals(MIN_SECONDARY_USER_ID, uidRangeOfSecondaryUser.getEndUser()); + + final UidRange uidRangeForDifferentUsers = new UidRange( + getUid(USER_SYSTEM, 10000), getUid(MIN_SECONDARY_USER_ID, 10100)); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getStartUser()); + assertEquals(MIN_SECONDARY_USER_ID, uidRangeOfSecondaryUser.getEndUser()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + public void testCreateForUser() throws Exception { + final UidRange uidRangeOfPrimaryUser = UidRange.createForUser(SYSTEM); + final UidRange uidRangeOfSecondaryUser = UidRange.createForUser( + UserHandle.of(USER_SYSTEM + 1)); + assertTrue(uidRangeOfPrimaryUser.stop < uidRangeOfSecondaryUser.start); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getStartUser()); + assertEquals(USER_SYSTEM, uidRangeOfPrimaryUser.getEndUser()); + assertEquals(USER_SYSTEM + 1, uidRangeOfSecondaryUser.getStartUser()); + assertEquals(USER_SYSTEM + 1, uidRangeOfSecondaryUser.getEndUser()); + } +} diff --git a/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt b/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt new file mode 100644 index 0000000000..87cfb345e5 --- /dev/null +++ b/tests/common/java/android/net/UnderlyingNetworkInfoTest.kt @@ -0,0 +1,50 @@ +/* + * 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 android.net + +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.assertParcelSane +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals + +private const val TEST_OWNER_UID = 123 +private const val TEST_IFACE = "test_tun0" +private val TEST_IFACE_LIST = listOf("wlan0", "rmnet_data0", "eth0") + +@SmallTest +@RunWith(DevSdkIgnoreRunner::class) +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +class UnderlyingNetworkInfoTest { + @Test + fun testParcelUnparcel() { + val testInfo = UnderlyingNetworkInfo(TEST_OWNER_UID, TEST_IFACE, TEST_IFACE_LIST) + assertEquals(TEST_OWNER_UID, testInfo.ownerUid) + assertEquals(TEST_IFACE, testInfo.iface) + assertEquals(TEST_IFACE_LIST, testInfo.underlyingIfaces) + assertParcelSane(testInfo, 3) + + val emptyInfo = UnderlyingNetworkInfo(0, String(), listOf()) + assertEquals(0, emptyInfo.ownerUid) + assertEquals(String(), emptyInfo.iface) + assertEquals(listOf(), emptyInfo.underlyingIfaces) + assertParcelSane(emptyInfo, 3) + } +} \ No newline at end of file diff --git a/tests/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/common/java/android/net/apf/ApfCapabilitiesTest.java new file mode 100644 index 0000000000..d50406fd3a --- /dev/null +++ b/tests/common/java/android/net/apf/ApfCapabilitiesTest.java @@ -0,0 +1,99 @@ +/* + * 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.apf; + +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ApfCapabilitiesTest { + private Context mContext; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + } + + @Test + public void testConstructAndParcel() { + final ApfCapabilities caps = new ApfCapabilities(123, 456, 789); + assertEquals(123, caps.apfVersionSupported); + assertEquals(456, caps.maximumApfProgramSize); + assertEquals(789, caps.apfPacketFormat); + + assertParcelSane(caps, 3); + } + + @Test + public void testEquals() { + assertEquals(new ApfCapabilities(1, 2, 3), new ApfCapabilities(1, 2, 3)); + assertNotEquals(new ApfCapabilities(2, 2, 3), new ApfCapabilities(1, 2, 3)); + assertNotEquals(new ApfCapabilities(1, 3, 3), new ApfCapabilities(1, 2, 3)); + assertNotEquals(new ApfCapabilities(1, 2, 4), new ApfCapabilities(1, 2, 3)); + } + + @Test + public void testHasDataAccess() { + //hasDataAccess is only supported starting at apf version 4. + ApfCapabilities caps = new ApfCapabilities(1 /* apfVersionSupported */, 2, 3); + assertFalse(caps.hasDataAccess()); + + caps = new ApfCapabilities(4 /* apfVersionSupported */, 5, 6); + assertTrue(caps.hasDataAccess()); + } + + @Test + public void testGetApfDrop8023Frames() { + // Get com.android.internal.R.bool.config_apfDrop802_3Frames. The test cannot directly + // use R.bool.config_apfDrop802_3Frames because that is not a stable resource ID. + final int resId = mContext.getResources().getIdentifier("config_apfDrop802_3Frames", + "bool", "android"); + final boolean shouldDrop8023Frames = mContext.getResources().getBoolean(resId); + final boolean actual = ApfCapabilities.getApfDrop8023Frames(); + assertEquals(shouldDrop8023Frames, actual); + } + + @Test + public void testGetApfEtherTypeBlackList() { + // Get com.android.internal.R.array.config_apfEthTypeBlackList. The test cannot directly + // use R.array.config_apfEthTypeBlackList because that is not a stable resource ID. + final int resId = mContext.getResources().getIdentifier("config_apfEthTypeBlackList", + "array", "android"); + final int[] blacklistedEtherTypeArray = mContext.getResources().getIntArray(resId); + final int[] actual = ApfCapabilities.getApfEtherTypeBlackList(); + assertNotNull(actual); + assertTrue(Arrays.equals(blacklistedEtherTypeArray, actual)); + } +} diff --git a/tests/common/java/android/net/metrics/ApfProgramEventTest.kt b/tests/common/java/android/net/metrics/ApfProgramEventTest.kt new file mode 100644 index 0000000000..0b7b74097c --- /dev/null +++ b/tests/common/java/android/net/metrics/ApfProgramEventTest.kt @@ -0,0 +1,72 @@ +/* + * 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.metrics; + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class ApfProgramEventTest { + private infix fun Int.hasFlag(flag: Int) = (this and (1 shl flag)) != 0 + + @Test + fun testBuilderAndParcel() { + val apfProgramEvent = ApfProgramEvent.Builder() + .setLifetime(1) + .setActualLifetime(2) + .setFilteredRas(3) + .setCurrentRas(4) + .setProgramLength(5) + .setFlags(true, true) + .build() + + assertEquals(1, apfProgramEvent.lifetime) + assertEquals(2, apfProgramEvent.actualLifetime) + assertEquals(3, apfProgramEvent.filteredRas) + assertEquals(4, apfProgramEvent.currentRas) + assertEquals(5, apfProgramEvent.programLength) + assertEquals(ApfProgramEvent.flagsFor(true, true), apfProgramEvent.flags) + + assertParcelSane(apfProgramEvent, 6) + } + + @Test + fun testFlagsFor() { + var flags = ApfProgramEvent.flagsFor(false, false) + assertFalse(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS) + assertFalse(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON) + + flags = ApfProgramEvent.flagsFor(true, false) + assertTrue(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS) + assertFalse(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON) + + flags = ApfProgramEvent.flagsFor(false, true) + assertFalse(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS) + assertTrue(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON) + + flags = ApfProgramEvent.flagsFor(true, true) + assertTrue(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS) + assertTrue(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON) + } +} diff --git a/tests/common/java/android/net/metrics/ApfStatsTest.kt b/tests/common/java/android/net/metrics/ApfStatsTest.kt new file mode 100644 index 0000000000..46a8c8e5b5 --- /dev/null +++ b/tests/common/java/android/net/metrics/ApfStatsTest.kt @@ -0,0 +1,57 @@ +/* + * 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.metrics + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class ApfStatsTest { + @Test + fun testBuilderAndParcel() { + val apfStats = ApfStats.Builder() + .setDurationMs(Long.MAX_VALUE) + .setReceivedRas(1) + .setMatchingRas(2) + .setDroppedRas(3) + .setZeroLifetimeRas(4) + .setParseErrors(5) + .setProgramUpdates(6) + .setProgramUpdatesAll(7) + .setProgramUpdatesAllowingMulticast(8) + .setMaxProgramSize(9) + .build() + + assertEquals(Long.MAX_VALUE, apfStats.durationMs) + assertEquals(1, apfStats.receivedRas) + assertEquals(2, apfStats.matchingRas) + assertEquals(3, apfStats.droppedRas) + assertEquals(4, apfStats.zeroLifetimeRas) + assertEquals(5, apfStats.parseErrors) + assertEquals(6, apfStats.programUpdates) + assertEquals(7, apfStats.programUpdatesAll) + assertEquals(8, apfStats.programUpdatesAllowingMulticast) + assertEquals(9, apfStats.maxProgramSize) + + assertParcelSane(apfStats, 10) + } +} diff --git a/tests/common/java/android/net/metrics/DhcpClientEventTest.kt b/tests/common/java/android/net/metrics/DhcpClientEventTest.kt new file mode 100644 index 0000000000..8d7a9c4050 --- /dev/null +++ b/tests/common/java/android/net/metrics/DhcpClientEventTest.kt @@ -0,0 +1,43 @@ +/* + * 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.metrics + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +private const val FAKE_MESSAGE = "test" + +@RunWith(AndroidJUnit4::class) +@SmallTest +class DhcpClientEventTest { + @Test + fun testBuilderAndParcel() { + val dhcpClientEvent = DhcpClientEvent.Builder() + .setMsg(FAKE_MESSAGE) + .setDurationMs(Integer.MAX_VALUE) + .build() + + assertEquals(FAKE_MESSAGE, dhcpClientEvent.msg) + assertEquals(Integer.MAX_VALUE, dhcpClientEvent.durationMs) + + assertParcelSane(dhcpClientEvent, 2) + } +} diff --git a/tests/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/common/java/android/net/metrics/DhcpErrorEventTest.kt new file mode 100644 index 0000000000..236f72eafb --- /dev/null +++ b/tests/common/java/android/net/metrics/DhcpErrorEventTest.kt @@ -0,0 +1,65 @@ +package android.net.metrics + +import android.net.metrics.DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH +import android.net.metrics.DhcpErrorEvent.errorCodeWithOption +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.parcelingRoundTrip +import java.lang.reflect.Modifier +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +private const val TEST_ERROR_CODE = 12345 +//DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java due to it's protected) +private const val DHCP_SUBNET_MASK = 1 + +@RunWith(AndroidJUnit4::class) +@SmallTest +class DhcpErrorEventTest { + + @Test + fun testConstructor() { + val event = DhcpErrorEvent(TEST_ERROR_CODE) + assertEquals(TEST_ERROR_CODE, event.errorCode) + } + + @Test + fun testParcelUnparcel() { + val event = DhcpErrorEvent(TEST_ERROR_CODE) + val parceled = parcelingRoundTrip(event) + assertEquals(TEST_ERROR_CODE, parceled.errorCode) + } + + @Test + fun testErrorCodeWithOption() { + val errorCode = errorCodeWithOption(DHCP_INVALID_OPTION_LENGTH, DHCP_SUBNET_MASK); + assertTrue((DHCP_INVALID_OPTION_LENGTH and errorCode) == DHCP_INVALID_OPTION_LENGTH); + assertTrue((DHCP_SUBNET_MASK and errorCode) == DHCP_SUBNET_MASK); + } + + @Test + fun testToString() { + val names = listOf("L2_ERROR", "L3_ERROR", "L4_ERROR", "DHCP_ERROR", "MISC_ERROR") + val errorFields = DhcpErrorEvent::class.java.declaredFields.filter { + it.type == Int::class.javaPrimitiveType + && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers) + && it.name !in names + } + + errorFields.forEach { + val intValue = it.getInt(null) + val stringValue = DhcpErrorEvent(intValue).toString() + assertTrue("Invalid string for error 0x%08X (field %s): %s".format(intValue, it.name, + stringValue), + stringValue.contains(it.name)) + } + } + + @Test + fun testToString_InvalidErrorCode() { + assertNotNull(DhcpErrorEvent(TEST_ERROR_CODE).toString()) + } +} diff --git a/tests/common/java/android/net/metrics/IpConnectivityLogTest.java b/tests/common/java/android/net/metrics/IpConnectivityLogTest.java new file mode 100644 index 0000000000..d4780d3a5d --- /dev/null +++ b/tests/common/java/android/net/metrics/IpConnectivityLogTest.java @@ -0,0 +1,161 @@ +/* + * 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.metrics; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import android.net.ConnectivityMetricsEvent; +import android.net.IIpConnectivityMetrics; +import android.net.Network; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.BitUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpConnectivityLogTest { + private static final int FAKE_NET_ID = 100; + private static final int[] FAKE_TRANSPORT_TYPES = BitUtils.unpackBits(TRANSPORT_WIFI); + private static final long FAKE_TIME_STAMP = System.currentTimeMillis(); + private static final String FAKE_INTERFACE_NAME = "test"; + private static final IpReachabilityEvent FAKE_EV = + new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED); + + @Mock IIpConnectivityMetrics mMockService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testLoggingEvents() throws Exception { + IpConnectivityLog logger = new IpConnectivityLog(mMockService); + + assertTrue(logger.log(FAKE_EV)); + assertTrue(logger.log(FAKE_TIME_STAMP, FAKE_EV)); + assertTrue(logger.log(FAKE_NET_ID, FAKE_TRANSPORT_TYPES, FAKE_EV)); + assertTrue(logger.log(new Network(FAKE_NET_ID), FAKE_TRANSPORT_TYPES, FAKE_EV)); + assertTrue(logger.log(FAKE_INTERFACE_NAME, FAKE_EV)); + assertTrue(logger.log(makeExpectedEvent(FAKE_TIME_STAMP, FAKE_NET_ID, TRANSPORT_WIFI, + FAKE_INTERFACE_NAME))); + + List got = verifyEvents(6); + assertEventsEqual(makeExpectedEvent(got.get(0).timestamp, 0, 0, null), got.get(0)); + assertEventsEqual(makeExpectedEvent(FAKE_TIME_STAMP, 0, 0, null), got.get(1)); + assertEventsEqual(makeExpectedEvent(got.get(2).timestamp, FAKE_NET_ID, + TRANSPORT_WIFI, null), got.get(2)); + assertEventsEqual(makeExpectedEvent(got.get(3).timestamp, FAKE_NET_ID, + TRANSPORT_WIFI, null), got.get(3)); + assertEventsEqual(makeExpectedEvent(got.get(4).timestamp, 0, 0, FAKE_INTERFACE_NAME), + got.get(4)); + assertEventsEqual(makeExpectedEvent(FAKE_TIME_STAMP, FAKE_NET_ID, + TRANSPORT_WIFI, FAKE_INTERFACE_NAME), got.get(5)); + } + + @Test + public void testLoggingEventsWithMultipleCallers() throws Exception { + IpConnectivityLog logger = new IpConnectivityLog(mMockService); + + final int nCallers = 10; + final int nEvents = 10; + for (int n = 0; n < nCallers; n++) { + final int i = n; + new Thread() { + public void run() { + for (int j = 0; j < nEvents; j++) { + assertTrue(logger.log(makeExpectedEvent( + FAKE_TIME_STAMP + i * 100 + j, + FAKE_NET_ID + i * 100 + j, + ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR, + FAKE_INTERFACE_NAME))); + } + } + }.start(); + } + + List got = verifyEvents(nCallers * nEvents, 200); + Collections.sort(got, EVENT_COMPARATOR); + Iterator iter = got.iterator(); + for (int i = 0; i < nCallers; i++) { + for (int j = 0; j < nEvents; j++) { + final long expectedTimestamp = FAKE_TIME_STAMP + i * 100 + j; + final int expectedNetId = FAKE_NET_ID + i * 100 + j; + final long expectedTransports = + ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR; + assertEventsEqual(makeExpectedEvent(expectedTimestamp, expectedNetId, + expectedTransports, FAKE_INTERFACE_NAME), iter.next()); + } + } + } + + private List verifyEvents(int n, int timeoutMs) throws Exception { + ArgumentCaptor captor = + ArgumentCaptor.forClass(ConnectivityMetricsEvent.class); + verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture()); + return captor.getAllValues(); + } + + private List verifyEvents(int n) throws Exception { + return verifyEvents(n, 10); + } + + + private ConnectivityMetricsEvent makeExpectedEvent(long timestamp, int netId, long transports, + String ifname) { + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.timestamp = timestamp; + ev.data = FAKE_EV; + ev.netId = netId; + ev.transports = transports; + ev.ifname = ifname; + return ev; + } + + /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */ + private void assertEventsEqual(ConnectivityMetricsEvent expected, + ConnectivityMetricsEvent got) { + assertEquals(expected.data, got.data); + assertEquals(expected.timestamp, got.timestamp); + assertEquals(expected.netId, got.netId); + assertEquals(expected.transports, got.transports); + assertEquals(expected.ifname, got.ifname); + } + + static final Comparator EVENT_COMPARATOR = + Comparator.comparingLong((ev) -> ev.timestamp); +} diff --git a/tests/common/java/android/net/metrics/IpManagerEventTest.kt b/tests/common/java/android/net/metrics/IpManagerEventTest.kt new file mode 100644 index 0000000000..64be50837f --- /dev/null +++ b/tests/common/java/android/net/metrics/IpManagerEventTest.kt @@ -0,0 +1,39 @@ +/* + * 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.metrics + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class IpManagerEventTest { + @Test + fun testConstructorAndParcel() { + (IpManagerEvent.PROVISIONING_OK..IpManagerEvent.ERROR_INTERFACE_NOT_FOUND).forEach { + val ipManagerEvent = IpManagerEvent(it, Long.MAX_VALUE) + assertEquals(it, ipManagerEvent.eventType) + assertEquals(Long.MAX_VALUE, ipManagerEvent.durationMs) + + assertParcelSane(ipManagerEvent, 2) + } + } +} diff --git a/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt b/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt new file mode 100644 index 0000000000..55b5e492dd --- /dev/null +++ b/tests/common/java/android/net/metrics/IpReachabilityEventTest.kt @@ -0,0 +1,38 @@ +/* + * 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.metrics + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class IpReachabilityEventTest { + @Test + fun testConstructorAndParcel() { + (IpReachabilityEvent.PROBE..IpReachabilityEvent.PROVISIONING_LOST_ORGANIC).forEach { + val ipReachabilityEvent = IpReachabilityEvent(it) + assertEquals(it, ipReachabilityEvent.eventType) + + assertParcelSane(ipReachabilityEvent, 1) + } + } +} diff --git a/tests/common/java/android/net/metrics/NetworkEventTest.kt b/tests/common/java/android/net/metrics/NetworkEventTest.kt new file mode 100644 index 0000000000..41430b03a1 --- /dev/null +++ b/tests/common/java/android/net/metrics/NetworkEventTest.kt @@ -0,0 +1,43 @@ +/* + * 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.metrics + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkEventTest { + @Test + fun testConstructorAndParcel() { + (NetworkEvent.NETWORK_CONNECTED..NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY).forEach { + var networkEvent = NetworkEvent(it) + assertEquals(it, networkEvent.eventType) + assertEquals(0, networkEvent.durationMs) + + networkEvent = NetworkEvent(it, Long.MAX_VALUE) + assertEquals(it, networkEvent.eventType) + assertEquals(Long.MAX_VALUE, networkEvent.durationMs) + + assertParcelSane(networkEvent, 2) + } + } +} diff --git a/tests/common/java/android/net/metrics/RaEventTest.kt b/tests/common/java/android/net/metrics/RaEventTest.kt new file mode 100644 index 0000000000..d9b720332f --- /dev/null +++ b/tests/common/java/android/net/metrics/RaEventTest.kt @@ -0,0 +1,72 @@ +/* + * 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.metrics + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +private const val NO_LIFETIME: Long = -1L + +@RunWith(AndroidJUnit4::class) +@SmallTest +class RaEventTest { + @Test + fun testConstructorAndParcel() { + var raEvent = RaEvent.Builder().build() + assertEquals(NO_LIFETIME, raEvent.routerLifetime) + assertEquals(NO_LIFETIME, raEvent.prefixValidLifetime) + assertEquals(NO_LIFETIME, raEvent.prefixPreferredLifetime) + assertEquals(NO_LIFETIME, raEvent.routeInfoLifetime) + assertEquals(NO_LIFETIME, raEvent.rdnssLifetime) + assertEquals(NO_LIFETIME, raEvent.dnsslLifetime) + + raEvent = RaEvent.Builder() + .updateRouterLifetime(1) + .updatePrefixValidLifetime(2) + .updatePrefixPreferredLifetime(3) + .updateRouteInfoLifetime(4) + .updateRdnssLifetime(5) + .updateDnsslLifetime(6) + .build() + assertEquals(1, raEvent.routerLifetime) + assertEquals(2, raEvent.prefixValidLifetime) + assertEquals(3, raEvent.prefixPreferredLifetime) + assertEquals(4, raEvent.routeInfoLifetime) + assertEquals(5, raEvent.rdnssLifetime) + assertEquals(6, raEvent.dnsslLifetime) + + raEvent = RaEvent.Builder() + .updateRouterLifetime(Long.MIN_VALUE) + .updateRouterLifetime(Long.MAX_VALUE) + .build() + assertEquals(Long.MIN_VALUE, raEvent.routerLifetime) + + raEvent = RaEvent(1, 2, 3, 4, 5, 6) + assertEquals(1, raEvent.routerLifetime) + assertEquals(2, raEvent.prefixValidLifetime) + assertEquals(3, raEvent.prefixPreferredLifetime) + assertEquals(4, raEvent.routeInfoLifetime) + assertEquals(5, raEvent.rdnssLifetime) + assertEquals(6, raEvent.dnsslLifetime) + + assertParcelSane(raEvent, 6) + } +} diff --git a/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt b/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt new file mode 100644 index 0000000000..51c0d41bf4 --- /dev/null +++ b/tests/common/java/android/net/metrics/ValidationProbeEventTest.kt @@ -0,0 +1,72 @@ +/* + * 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.metrics + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.assertParcelSane +import java.lang.reflect.Modifier +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +private const val FIRST_VALIDATION: Int = 1 shl 8 +private const val REVALIDATION: Int = 2 shl 8 + +@RunWith(AndroidJUnit4::class) +@SmallTest +class ValidationProbeEventTest { + private infix fun Int.hasType(type: Int) = (type and this) == type + + @Test + fun testBuilderAndParcel() { + var validationProbeEvent = ValidationProbeEvent.Builder() + .setProbeType(ValidationProbeEvent.PROBE_DNS, false).build() + + assertTrue(validationProbeEvent.probeType hasType REVALIDATION) + + validationProbeEvent = ValidationProbeEvent.Builder() + .setDurationMs(Long.MAX_VALUE) + .setProbeType(ValidationProbeEvent.PROBE_DNS, true) + .setReturnCode(ValidationProbeEvent.DNS_SUCCESS) + .build() + + assertEquals(Long.MAX_VALUE, validationProbeEvent.durationMs) + assertTrue(validationProbeEvent.probeType hasType ValidationProbeEvent.PROBE_DNS) + assertTrue(validationProbeEvent.probeType hasType FIRST_VALIDATION) + assertEquals(ValidationProbeEvent.DNS_SUCCESS, validationProbeEvent.returnCode) + + assertParcelSane(validationProbeEvent, 3) + } + + @Test + fun testGetProbeName() { + val probeFields = ValidationProbeEvent::class.java.declaredFields.filter { + it.type == Int::class.javaPrimitiveType + && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers) + && it.name.contains("PROBE") + } + + probeFields.forEach { + val intValue = it.getInt(null) + val stringValue = ValidationProbeEvent.getProbeName(intValue) + assertEquals(it.name, stringValue) + } + + } +} diff --git a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt new file mode 100644 index 0000000000..7b22e45db9 --- /dev/null +++ b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt @@ -0,0 +1,209 @@ +/* + * 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 android.net.netstats + +import android.net.NetworkStats +import android.net.NetworkStats.DEFAULT_NETWORK_NO +import android.net.NetworkStats.DEFAULT_NETWORK_YES +import android.net.NetworkStats.Entry +import android.net.NetworkStats.IFACE_VT +import android.net.NetworkStats.METERED_NO +import android.net.NetworkStats.METERED_YES +import android.net.NetworkStats.ROAMING_NO +import android.net.NetworkStats.ROAMING_YES +import android.net.NetworkStats.SET_DEFAULT +import android.net.NetworkStats.SET_FOREGROUND +import android.net.NetworkStats.TAG_NONE +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.assertFieldCountEquals +import com.android.testutils.assertNetworkStatsEquals +import com.android.testutils.assertParcelingIsLossless +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import kotlin.test.assertEquals + +@RunWith(JUnit4::class) +@SmallTest +class NetworkStatsApiTest { + @Rule + @JvmField + val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q) + + private val testStatsEmpty = NetworkStats(0L, 0) + + // Note that these variables need to be initialized outside of constructor, initialize + // here with methods that don't exist in Q devices will result in crash. + + // stats1 and stats2 will have some entries with common keys, which are expected to + // be merged if performing add on these 2 stats. + private lateinit var testStats1: NetworkStats + private lateinit var testStats2: NetworkStats + + // This is a result of adding stats1 and stats2, while the merging of common key items is + // subject to test later, this should not be initialized with for a loop to add stats1 + // and stats2 above. + private lateinit var testStats3: NetworkStats + + companion object { + private const val TEST_IFACE = "test0" + private const val TEST_UID1 = 1001 + private const val TEST_UID2 = 1002 + } + + @Before + fun setUp() { + testStats1 = NetworkStats(0L, 0) + // Entries which only appear in set1. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4)) + // Entries which are common for set1 and set2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0)) + assertEquals(8, testStats1.size()) + + testStats2 = NetworkStats(0L, 0) + // Entries which are common for set1 and set2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0)) + // Entry which only appears in set2. + .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + assertEquals(5, testStats2.size()) + + testStats3 = NetworkStats(0L, 9) + // Entries which are unique either in stats1 or stats2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) + .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + // Entries which are common for stats1 and stats2 are being merged. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0)) + assertEquals(9, testStats3.size()) + } + + @Test + fun testAddEntry() { + val expectedEntriesInStats2 = arrayOf( + Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1), + Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45), + Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7), + Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0), + Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + + // While testStats* are already initialized with addEntry, verify content added + // matches expectation. + for (i in expectedEntriesInStats2.indices) { + val entry = testStats2.getValues(i, null) + assertEquals(expectedEntriesInStats2[i], entry) + } + + // Verify entry updated with addEntry. + val stats = testStats2.addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12, -5, 7, 0, 9)) + assertEquals(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16, -2, 9, 1, 9), + stats.getValues(3, null)) + } + + @Test + fun testAdd() { + var stats = NetworkStats(0L, 0) + assertNetworkStatsEquals(testStatsEmpty, stats) + stats = stats.add(testStats2) + assertNetworkStatsEquals(testStats2, stats) + stats = stats.add(testStats1) + // EMPTY + STATS2 + STATS1 = STATS3 + assertNetworkStatsEquals(testStats3, stats) + } + + @Test + fun testParcelUnparcel() { + assertParcelingIsLossless(testStatsEmpty) + assertParcelingIsLossless(testStats1) + assertParcelingIsLossless(testStats2) + assertFieldCountEquals(15, NetworkStats::class.java) + } + + @Test + fun testDescribeContents() { + assertEquals(0, testStatsEmpty.describeContents()) + assertEquals(0, testStats1.describeContents()) + assertEquals(0, testStats2.describeContents()) + assertEquals(0, testStats3.describeContents()) + } + + @Test + fun testSubtract() { + // STATS3 - STATS2 = STATS1 + assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2)) + // STATS3 - STATS1 = STATS2 + assertNetworkStatsEquals(testStats2, testStats3.subtract(testStats1)) + } + + @Test + fun testMethodsDontModifyReceiver() { + listOf(testStatsEmpty, testStats1, testStats2, testStats3).forEach { + val origStats = it.clone() + it.addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45)) + it.add(testStats3) + it.subtract(testStats1) + assertNetworkStatsEquals(origStats, it) + } + } +} \ No newline at end of file diff --git a/tests/common/java/android/net/util/SocketUtilsTest.kt b/tests/common/java/android/net/util/SocketUtilsTest.kt new file mode 100644 index 0000000000..aaf97f3688 --- /dev/null +++ b/tests/common/java/android/net/util/SocketUtilsTest.kt @@ -0,0 +1,90 @@ +/* + * 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.os.Build +import android.system.NetlinkSocketAddress +import android.system.Os +import android.system.OsConstants.AF_INET +import android.system.OsConstants.ETH_P_ALL +import android.system.OsConstants.IPPROTO_UDP +import android.system.OsConstants.RTMGRP_NEIGH +import android.system.OsConstants.SOCK_DGRAM +import android.system.PacketSocketAddress +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +private const val TEST_INDEX = 123 +private const val TEST_PORT = 555 +private const val FF_BYTE = 0xff.toByte() + +@RunWith(AndroidJUnit4::class) +@SmallTest +class SocketUtilsTest { + @Rule @JvmField + val ignoreRule = DevSdkIgnoreRule() + + @Test + fun testMakeNetlinkSocketAddress() { + val nlAddress = SocketUtils.makeNetlinkSocketAddress(TEST_PORT, RTMGRP_NEIGH) + if (nlAddress is NetlinkSocketAddress) { + assertEquals(TEST_PORT, nlAddress.getPortId()) + assertEquals(RTMGRP_NEIGH, nlAddress.getGroupsMask()) + } else { + fail("Not NetlinkSocketAddress object") + } + } + + @Test + fun testMakePacketSocketAddress_Q() { + val pkAddress = SocketUtils.makePacketSocketAddress(ETH_P_ALL, TEST_INDEX) + assertTrue("Not PacketSocketAddress object", pkAddress is PacketSocketAddress) + + val pkAddress2 = SocketUtils.makePacketSocketAddress(TEST_INDEX, ByteArray(6) { FF_BYTE }) + assertTrue("Not PacketSocketAddress object", pkAddress2 is PacketSocketAddress) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + fun testMakePacketSocketAddress() { + val pkAddress = SocketUtils.makePacketSocketAddress( + ETH_P_ALL, TEST_INDEX, ByteArray(6) { FF_BYTE }) + assertTrue("Not PacketSocketAddress object", pkAddress is PacketSocketAddress) + } + + @Test + fun testCloseSocket() { + // Expect no exception happening with null object. + SocketUtils.closeSocket(null) + + val fd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) + assertTrue(fd.valid()) + SocketUtils.closeSocket(fd) + assertFalse(fd.valid()) + // Expecting socket should be still invalid even closed socket again. + SocketUtils.closeSocket(fd) + assertFalse(fd.valid()) + } +} diff --git a/tests/deflake/Android.bp b/tests/deflake/Android.bp new file mode 100644 index 0000000000..58ece37ef6 --- /dev/null +++ b/tests/deflake/Android.bp @@ -0,0 +1,39 @@ +// +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_test_host { + name: "FrameworksNetDeflakeTest", + srcs: ["src/**/*.kt"], + libs: [ + "junit", + "tradefed", + ], + static_libs: [ + "kotlin-test", + "net-host-tests-utils", + ], + data: [":FrameworksNetTests"], + test_suites: ["device-tests"], +} diff --git a/tests/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt b/tests/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt new file mode 100644 index 0000000000..62855255fe --- /dev/null +++ b/tests/deflake/src/com/android/server/net/FrameworksNetDeflakeTest.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net + +import com.android.testutils.host.DeflakeHostTestBase +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import org.junit.runner.RunWith + +@RunWith(DeviceJUnit4ClassRunner::class) +class FrameworksNetDeflakeTest: DeflakeHostTestBase() { + override val runCount = 20 + override val testApkFilename = "FrameworksNetTests.apk" + override val testClasses = listOf("com.android.server.ConnectivityServiceTest") +} \ No newline at end of file diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp new file mode 100644 index 0000000000..56f9df78c8 --- /dev/null +++ b/tests/integration/Android.bp @@ -0,0 +1,76 @@ +// +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "FrameworksNetIntegrationTests", + platform_apis: true, + certificate: "platform", + srcs: [ + "src/**/*.kt", + "src/**/*.aidl", + ], + libs: [ + "android.test.mock", + ], + static_libs: [ + "NetworkStackApiStableLib", + "androidx.test.ext.junit", + "frameworks-net-integration-testutils", + "kotlin-reflect", + "mockito-target-extended-minus-junit4", + "net-tests-utils", + "service-connectivity", + "services.core", + "services.net", + "testables", + ], + test_suites: ["device-tests"], + use_embedded_native_libs: true, + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + // android_library does not include JNI libs: include NetworkStack dependencies here + "libnativehelper_compat_libc++", + "libnetworkstackutilsjni", + ], +} + +// Utilities for testing framework code both in integration and unit tests. +java_library { + name: "frameworks-net-integration-testutils", + srcs: ["util/**/*.java", "util/**/*.kt"], + static_libs: [ + "androidx.annotation_annotation", + "androidx.test.rules", + "junit", + "net-tests-utils", + ], + libs: [ + "service-connectivity", + "services.core", + "services.net", + ], +} diff --git a/tests/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml new file mode 100644 index 0000000000..2e13689357 --- /dev/null +++ b/tests/integration/AndroidManifest.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/integration/res/values/config.xml b/tests/integration/res/values/config.xml new file mode 100644 index 0000000000..2c8046ffd7 --- /dev/null +++ b/tests/integration/res/values/config.xml @@ -0,0 +1,15 @@ + + + + 12500 + http://test.android.com + https://secure.test.android.com + + http://fallback1.android.com + http://fallback2.android.com + + + + diff --git a/tests/integration/src/android/net/TestNetworkStackClient.kt b/tests/integration/src/android/net/TestNetworkStackClient.kt new file mode 100644 index 0000000000..01eb514a1c --- /dev/null +++ b/tests/integration/src/android/net/TestNetworkStackClient.kt @@ -0,0 +1,80 @@ +/* + * 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 + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.IBinder +import com.android.server.net.integrationtests.TestNetworkStackService +import org.mockito.Mockito.any +import org.mockito.Mockito.spy +import org.mockito.Mockito.timeout +import org.mockito.Mockito.verify +import kotlin.test.fail + +const val TEST_ACTION_SUFFIX = ".Test" + +class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) { + // TODO: consider switching to TrackRecord for more expressive checks + private val lastCallbacks = HashMap() + + private class TestDependencies(private val context: Context) : Dependencies { + override fun addToServiceManager(service: IBinder) = Unit + override fun checkCallerUid() = Unit + + override fun getConnectivityModuleConnector(): ConnectivityModuleConnector { + return ConnectivityModuleConnector { _, _, _, inSystemProcess -> + getNetworkStackIntent(inSystemProcess) + }.also { it.init(context) } + } + + private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? { + // Simulate out-of-system-process config: in-process service not found (null intent) + if (inSystemProcess) return null + val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX) + val serviceName = TestNetworkStackService::class.qualifiedName + ?: fail("TestNetworkStackService name not found") + intent.component = ComponentName(context.packageName, serviceName) + return intent + } + } + + // base may be an instance of an inaccessible subclass, so non-spyable. + // Use a known open class that delegates to the original instance for all methods except + // asBinder. asBinder needs to use its own non-delegated implementation as otherwise it would + // return a binder token to a class that is not spied on. + open class NetworkMonitorCallbacksWrapper(private val base: INetworkMonitorCallbacks) : + INetworkMonitorCallbacks.Stub(), INetworkMonitorCallbacks by base { + // asBinder is implemented by both base class and delegate: specify explicitly + override fun asBinder(): IBinder { + return super.asBinder() + } + } + + override fun makeNetworkMonitor(network: Network, name: String?, cb: INetworkMonitorCallbacks) { + val cbSpy = spy(NetworkMonitorCallbacksWrapper(cb)) + lastCallbacks[network] = cbSpy + super.makeNetworkMonitor(network, name, cbSpy) + } + + fun verifyNetworkMonitorCreated(network: Network, timeoutMs: Long) { + val cb = lastCallbacks[network] + ?: fail("NetworkMonitor for network $network not requested") + verify(cb, timeout(timeoutMs)).onNetworkMonitorCreated(any()) + } +} \ No newline at end of file diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt new file mode 100644 index 0000000000..b6e42743e2 --- /dev/null +++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests + +import android.app.usage.NetworkStatsManager +import android.content.ComponentName +import android.content.Context +import android.content.Context.BIND_AUTO_CREATE +import android.content.Context.BIND_IMPORTANT +import android.content.Intent +import android.content.ServiceConnection +import android.net.ConnectivityManager +import android.net.IDnsResolver +import android.net.INetd +import android.net.LinkProperties +import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL +import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkRequest +import android.net.TestNetworkStackClient +import android.net.Uri +import android.net.metrics.IpConnectivityLog +import android.os.ConditionVariable +import android.os.IBinder +import android.os.SystemConfigManager +import android.os.UserHandle +import android.testing.TestableContext +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.ConnectivityService +import com.android.server.NetworkAgentWrapper +import com.android.server.TestNetIdManager +import com.android.server.connectivity.MockableSystemProperties +import com.android.server.connectivity.ProxyTracker +import com.android.testutils.TestableNetworkCallback +import org.junit.After +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.AdditionalAnswers +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.doNothing +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.MockitoAnnotations +import org.mockito.Spy +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import kotlin.test.fail + +const val SERVICE_BIND_TIMEOUT_MS = 5_000L +const val TEST_TIMEOUT_MS = 10_000L + +/** + * Test that exercises an instrumented version of ConnectivityService against an instrumented + * NetworkStack in a different test process. + */ +@RunWith(AndroidJUnit4::class) +class ConnectivityServiceIntegrationTest { + // lateinit used here for mocks as they need to be reinitialized between each test and the test + // should crash if they are used before being initialized. + @Mock + private lateinit var statsManager: NetworkStatsManager + @Mock + private lateinit var log: IpConnectivityLog + @Mock + private lateinit var netd: INetd + @Mock + private lateinit var dnsResolver: IDnsResolver + @Mock + private lateinit var systemConfigManager: SystemConfigManager + @Spy + private var context = TestableContext(realContext) + + // lateinit for these three classes under test, as they should be reset to a different instance + // for every test but should always be initialized before use (or the test should crash). + private lateinit var networkStackClient: TestNetworkStackClient + private lateinit var service: ConnectivityService + private lateinit var cm: ConnectivityManager + + companion object { + // lateinit for this binder token, as it must be initialized before any test code is run + // and use of it before init should crash the test. + private lateinit var nsInstrumentation: INetworkStackInstrumentation + private val bindingCondition = ConditionVariable(false) + + private val realContext get() = InstrumentationRegistry.getInstrumentation().context + private val httpProbeUrl get() = + realContext.getResources().getString(R.string.config_captive_portal_http_url) + private val httpsProbeUrl get() = + realContext.getResources().getString(R.string.config_captive_portal_https_url) + + private class InstrumentationServiceConnection : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + Log.i("TestNetworkStack", "Service connected") + try { + if (service == null) fail("Error binding to NetworkStack instrumentation") + if (::nsInstrumentation.isInitialized) fail("Service already connected") + nsInstrumentation = INetworkStackInstrumentation.Stub.asInterface(service) + } finally { + bindingCondition.open() + } + } + + override fun onServiceDisconnected(name: ComponentName?) = Unit + } + + @BeforeClass + @JvmStatic + fun setUpClass() { + val intent = Intent(realContext, NetworkStackInstrumentationService::class.java) + intent.action = INetworkStackInstrumentation::class.qualifiedName + assertTrue(realContext.bindService(intent, InstrumentationServiceConnection(), + BIND_AUTO_CREATE or BIND_IMPORTANT), + "Error binding to instrumentation service") + assertTrue(bindingCondition.block(SERVICE_BIND_TIMEOUT_MS), + "Timed out binding to instrumentation service " + + "after $SERVICE_BIND_TIMEOUT_MS ms") + } + } + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + val asUserCtx = mock(Context::class.java, AdditionalAnswers.delegatesTo(context)) + doReturn(UserHandle.ALL).`when`(asUserCtx).user + doReturn(asUserCtx).`when`(context).createContextAsUser(eq(UserHandle.ALL), anyInt()) + doNothing().`when`(context).sendStickyBroadcast(any(), any()) + doReturn(Context.SYSTEM_CONFIG_SERVICE).`when`(context) + .getSystemServiceName(SystemConfigManager::class.java) + doReturn(systemConfigManager).`when`(context) + .getSystemService(Context.SYSTEM_CONFIG_SERVICE) + doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString()) + + networkStackClient = TestNetworkStackClient(realContext) + networkStackClient.init() + networkStackClient.start() + + service = TestConnectivityService(makeDependencies()) + cm = ConnectivityManager(context, service) + context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm) + context.addMockSystemService(Context.NETWORK_STATS_SERVICE, statsManager) + + service.systemReadyInternal() + } + + private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService( + context, dnsResolver, log, netd, deps) + + private fun makeDependencies(): ConnectivityService.Dependencies { + val deps = spy(ConnectivityService.Dependencies()) + doReturn(networkStackClient).`when`(deps).networkStack + doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any()) + doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties + doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager() + return deps + } + + @After + fun tearDown() { + nsInstrumentation.clearAllState() + } + + @Test + fun testValidation() { + val request = NetworkRequest.Builder() + .clearCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .build() + val testCallback = TestableNetworkCallback() + + cm.registerNetworkCallback(request, testCallback) + nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204)) + nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204)) + + val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), null /* ncTemplate */, + context) + networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS) + + na.addCapability(NET_CAPABILITY_INTERNET) + na.connect() + + testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS) + assertEquals(2, nsInstrumentation.getRequestUrls().size) + } + + @Test + fun testCapportApi() { + val request = NetworkRequest.Builder() + .clearCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .build() + val testCb = TestableNetworkCallback() + val apiUrl = "https://capport.android.com" + + cm.registerNetworkCallback(request, testCb) + nsInstrumentation.addHttpResponse(HttpResponse( + apiUrl, + """ + |{ + | "captive": true, + | "user-portal-url": "https://login.capport.android.com", + | "venue-info-url": "https://venueinfo.capport.android.com" + |} + """.trimMargin())) + + // Tests will fail if a non-mocked query is received: mock the HTTPS probe, but not the + // HTTP probe as it should not be sent. + // Even if the HTTPS probe succeeds, a portal should be detected as the API takes precedence + // in that case. + nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204)) + + val lp = LinkProperties() + lp.captivePortalApiUrl = Uri.parse(apiUrl) + val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, lp, null /* ncTemplate */, context) + networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS) + + na.addCapability(NET_CAPABILITY_INTERNET) + na.connect() + + testCb.expectAvailableCallbacks(na.network, validated = false, tmt = TEST_TIMEOUT_MS) + + val capportData = testCb.expectLinkPropertiesThat(na, TEST_TIMEOUT_MS) { + it.captivePortalData != null + }.lp.captivePortalData + assertNotNull(capportData) + assertTrue(capportData.isCaptive) + assertEquals(Uri.parse("https://login.capport.android.com"), capportData.userPortalUrl) + assertEquals(Uri.parse("https://venueinfo.capport.android.com"), capportData.venueInfoUrl) + + val nc = testCb.expectCapabilitiesWith(NET_CAPABILITY_CAPTIVE_PORTAL, na, TEST_TIMEOUT_MS) + assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED)) + } +} \ No newline at end of file diff --git a/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl b/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl new file mode 100644 index 0000000000..9a2bcfea76 --- /dev/null +++ b/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests; + +parcelable HttpResponse; \ No newline at end of file diff --git a/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.kt b/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.kt new file mode 100644 index 0000000000..e2063138fe --- /dev/null +++ b/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests + +import android.os.Parcel +import android.os.Parcelable + +data class HttpResponse( + val requestUrl: String, + val responseCode: Int, + val content: String = "", + val redirectUrl: String? = null +) : Parcelable { + constructor(p: Parcel): this(p.readString(), p.readInt(), p.readString(), p.readString()) + constructor(requestUrl: String, contentBody: String): this( + requestUrl, + responseCode = 200, + content = contentBody, + redirectUrl = null) + + override fun writeToParcel(dest: Parcel, flags: Int) { + with(dest) { + writeString(requestUrl) + writeInt(responseCode) + writeString(content) + writeString(redirectUrl) + } + } + + override fun describeContents() = 0 + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(source: Parcel) = HttpResponse(source) + override fun newArray(size: Int) = arrayOfNulls(size) + } +} \ No newline at end of file diff --git a/tests/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl b/tests/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl new file mode 100644 index 0000000000..efc58add9c --- /dev/null +++ b/tests/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests; + +import com.android.server.net.integrationtests.HttpResponse; + +interface INetworkStackInstrumentation { + void clearAllState(); + void addHttpResponse(in HttpResponse response); + List getRequestUrls(); +} \ No newline at end of file diff --git a/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt b/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt new file mode 100644 index 0000000000..e807952cec --- /dev/null +++ b/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests + +import android.app.Service +import android.content.Intent +import java.net.URL +import java.util.Collections +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedQueue +import kotlin.collections.ArrayList +import kotlin.test.fail + +/** + * An instrumentation interface for the NetworkStack that allows controlling behavior to + * facilitate integration tests. + */ +class NetworkStackInstrumentationService : Service() { + override fun onBind(intent: Intent) = InstrumentationConnector.asBinder() + + object InstrumentationConnector : INetworkStackInstrumentation.Stub() { + private val httpResponses = ConcurrentHashMap>() + .run { + withDefault { key -> getOrPut(key) { ConcurrentLinkedQueue() } } + } + private val httpRequestUrls = Collections.synchronizedList(ArrayList()) + + /** + * Called when an HTTP request is being processed by NetworkMonitor. Returns the response + * that should be simulated. + */ + fun processRequest(url: URL): HttpResponse { + val strUrl = url.toString() + httpRequestUrls.add(strUrl) + return httpResponses[strUrl]?.poll() + ?: fail("No mocked response for request: $strUrl. " + + "Mocked URL keys are: ${httpResponses.keys}") + } + + /** + * Clear all state of this connector. This is intended for use between two tests, so all + * state should be reset as if the connector was just created. + */ + override fun clearAllState() { + httpResponses.clear() + httpRequestUrls.clear() + } + + /** + * Add a response to a future HTTP request. + * + *

      For any subsequent HTTP/HTTPS query, the first response with a matching URL will be + * used to mock the query response. + * + *

      All requests that are expected to be sent must have a mock response: if an unexpected + * request is seen, the test will fail. + */ + override fun addHttpResponse(response: HttpResponse) { + httpResponses.getValue(response.requestUrl).add(response) + } + + /** + * Get the ordered list of request URLs that have been sent by NetworkMonitor, and were + * answered based on mock responses. + */ + override fun getRequestUrls(): List { + return ArrayList(httpRequestUrls) + } + } +} \ No newline at end of file diff --git a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt new file mode 100644 index 0000000000..eff66584d6 --- /dev/null +++ b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net.integrationtests + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.net.INetworkMonitorCallbacks +import android.net.Network +import android.net.metrics.IpConnectivityLog +import android.net.util.SharedLog +import android.os.IBinder +import com.android.networkstack.netlink.TcpSocketTracker +import com.android.server.NetworkStackService +import com.android.server.NetworkStackService.NetworkMonitorConnector +import com.android.server.NetworkStackService.NetworkStackConnector +import com.android.server.connectivity.NetworkMonitor +import com.android.server.net.integrationtests.NetworkStackInstrumentationService.InstrumentationConnector +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import java.io.ByteArrayInputStream +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLConnection +import java.nio.charset.StandardCharsets + +private const val TEST_NETID = 42 + +/** + * Android service that can return an [android.net.INetworkStackConnector] which can be instrumented + * through [NetworkStackInstrumentationService]. + * Useful in tests to create test instrumented NetworkStack components that can receive + * instrumentation commands through [NetworkStackInstrumentationService]. + */ +class TestNetworkStackService : Service() { + override fun onBind(intent: Intent): IBinder = TestNetworkStackConnector(makeTestContext()) + + private fun makeTestContext() = spy(applicationContext).also { + doReturn(mock(IBinder::class.java)).`when`(it).getSystemService(Context.NETD_SERVICE) + } + + private class TestPermissionChecker : NetworkStackService.PermissionChecker() { + override fun enforceNetworkStackCallingPermission() = Unit + } + + private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) : + NetworkMonitor.Dependencies() { + override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork + } + + private inner class TestNetworkStackConnector(context: Context) : NetworkStackConnector( + context, TestPermissionChecker(), NetworkStackService.Dependencies()) { + + private val network = Network(TEST_NETID) + private val privateDnsBypassNetwork = TestNetwork(TEST_NETID) + + private inner class TestNetwork(netId: Int) : Network(netId) { + override fun openConnection(url: URL): URLConnection { + val response = InstrumentationConnector.processRequest(url) + val responseBytes = response.content.toByteArray(StandardCharsets.UTF_8) + + val connection = mock(HttpURLConnection::class.java) + doReturn(response.responseCode).`when`(connection).responseCode + doReturn(responseBytes.size.toLong()).`when`(connection).contentLengthLong + doReturn(response.redirectUrl).`when`(connection).getHeaderField("location") + doReturn(ByteArrayInputStream(responseBytes)).`when`(connection).inputStream + return connection + } + } + + override fun makeNetworkMonitor( + network: Network, + name: String?, + cb: INetworkMonitorCallbacks + ) { + val nm = NetworkMonitor(this@TestNetworkStackService, cb, + this.network, + mock(IpConnectivityLog::class.java), mock(SharedLog::class.java), + mock(NetworkStackService.NetworkStackServiceManager::class.java), + NetworkMonitorDeps(privateDnsBypassNetwork), + mock(TcpSocketTracker::class.java)) + cb.onNetworkMonitorCreated(NetworkMonitorConnector(nm, TestPermissionChecker())) + } + } +} diff --git a/tests/integration/util/com/android/server/ConnectivityServiceTestUtils.kt b/tests/integration/util/com/android/server/ConnectivityServiceTestUtils.kt new file mode 100644 index 0000000000..165fd37282 --- /dev/null +++ b/tests/integration/util/com/android/server/ConnectivityServiceTestUtils.kt @@ -0,0 +1,43 @@ +/* + * 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 + */ + +@file:JvmName("ConnectivityServiceTestUtils") + +package com.android.server + +import android.net.ConnectivityManager.TYPE_BLUETOOTH +import android.net.ConnectivityManager.TYPE_ETHERNET +import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_NONE +import android.net.ConnectivityManager.TYPE_TEST +import android.net.ConnectivityManager.TYPE_VPN +import android.net.ConnectivityManager.TYPE_WIFI +import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_ETHERNET +import android.net.NetworkCapabilities.TRANSPORT_TEST +import android.net.NetworkCapabilities.TRANSPORT_VPN +import android.net.NetworkCapabilities.TRANSPORT_WIFI + +fun transportToLegacyType(transport: Int) = when (transport) { + TRANSPORT_BLUETOOTH -> TYPE_BLUETOOTH + TRANSPORT_CELLULAR -> TYPE_MOBILE + TRANSPORT_ETHERNET -> TYPE_ETHERNET + TRANSPORT_TEST -> TYPE_TEST + TRANSPORT_VPN -> TYPE_VPN + TRANSPORT_WIFI -> TYPE_WIFI + else -> TYPE_NONE +} \ No newline at end of file diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java new file mode 100644 index 0000000000..e80955014f --- /dev/null +++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; + +import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType; + +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkProvider; +import android.net.NetworkSpecifier; +import android.net.QosFilter; +import android.net.SocketKeepalive; +import android.os.ConditionVariable; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; +import android.util.Range; + +import com.android.net.module.util.ArrayTrackRecord; +import com.android.server.connectivity.ConnectivityConstants; +import com.android.testutils.HandlerUtils; +import com.android.testutils.TestableNetworkCallback; + +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { + private final NetworkCapabilities mNetworkCapabilities; + private final HandlerThread mHandlerThread; + private final Context mContext; + private final String mLogTag; + private final NetworkAgentConfig mNetworkAgentConfig; + + private final ConditionVariable mDisconnected = new ConditionVariable(); + private final ConditionVariable mPreventReconnectReceived = new ConditionVariable(); + private final AtomicBoolean mConnected = new AtomicBoolean(false); + private int mScore; + private NetworkAgent mNetworkAgent; + private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED; + private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE; + // Controls how test network agent is going to wait before responding to keepalive + // start/stop. Useful when simulate KeepaliveTracker is waiting for response from modem. + private long mKeepaliveResponseDelay = 0L; + private Integer mExpectedKeepaliveSlot = null; + private final ArrayTrackRecord.ReadHead mCallbackHistory = + new ArrayTrackRecord().newReadHead(); + + public NetworkAgentWrapper(int transport, LinkProperties linkProperties, + NetworkCapabilities ncTemplate, Context context) throws Exception { + final int type = transportToLegacyType(transport); + final String typeName = ConnectivityManager.getNetworkTypeName(type); + mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities(); + mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + mNetworkCapabilities.addTransportType(transport); + switch (transport) { + case TRANSPORT_ETHERNET: + mScore = 70; + break; + case TRANSPORT_WIFI: + mScore = 60; + break; + case TRANSPORT_CELLULAR: + mScore = 50; + break; + case TRANSPORT_WIFI_AWARE: + mScore = 20; + break; + case TRANSPORT_VPN: + mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN); + // VPNs deduce the SUSPENDED capability from their underlying networks and there + // is no public API to let VPN services set it. + mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + mScore = ConnectivityConstants.VPN_DEFAULT_SCORE; + break; + default: + throw new UnsupportedOperationException("unimplemented network type"); + } + mContext = context; + mLogTag = "Mock-" + typeName; + mHandlerThread = new HandlerThread(mLogTag); + mHandlerThread.start(); + + // extraInfo is set to "" by default in NetworkAgentConfig. + final String extraInfo = (transport == TRANSPORT_CELLULAR) ? "internet.apn" : ""; + mNetworkAgentConfig = new NetworkAgentConfig.Builder() + .setLegacyType(type) + .setLegacyTypeName(typeName) + .setLegacyExtraInfo(extraInfo) + .build(); + mNetworkAgent = makeNetworkAgent(linkProperties, mNetworkAgentConfig); + } + + protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties, + final NetworkAgentConfig nac) throws Exception { + return new InstrumentedNetworkAgent(this, linkProperties, nac); + } + + public static class InstrumentedNetworkAgent extends NetworkAgent { + private final NetworkAgentWrapper mWrapper; + private static final String PROVIDER_NAME = "InstrumentedNetworkAgentProvider"; + + public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp, + NetworkAgentConfig nac) { + super(wrapper.mContext, wrapper.mHandlerThread.getLooper(), wrapper.mLogTag, + wrapper.mNetworkCapabilities, lp, wrapper.mScore, nac, + new NetworkProvider(wrapper.mContext, wrapper.mHandlerThread.getLooper(), + PROVIDER_NAME)); + mWrapper = wrapper; + register(); + } + + @Override + public void unwanted() { + mWrapper.mDisconnected.open(); + } + + @Override + public void startSocketKeepalive(Message msg) { + int slot = msg.arg1; + if (mWrapper.mExpectedKeepaliveSlot != null) { + assertEquals((int) mWrapper.mExpectedKeepaliveSlot, slot); + } + mWrapper.mHandlerThread.getThreadHandler().postDelayed( + () -> onSocketKeepaliveEvent(slot, mWrapper.mStartKeepaliveError), + mWrapper.mKeepaliveResponseDelay); + } + + @Override + public void stopSocketKeepalive(Message msg) { + final int slot = msg.arg1; + mWrapper.mHandlerThread.getThreadHandler().postDelayed( + () -> onSocketKeepaliveEvent(slot, mWrapper.mStopKeepaliveError), + mWrapper.mKeepaliveResponseDelay); + } + + @Override + public void onQosCallbackRegistered(final int qosCallbackId, + final @NonNull QosFilter filter) { + Log.i(mWrapper.mLogTag, "onQosCallbackRegistered"); + mWrapper.mCallbackHistory.add( + new CallbackType.OnQosCallbackRegister(qosCallbackId, filter)); + } + + @Override + public void onQosCallbackUnregistered(final int qosCallbackId) { + Log.i(mWrapper.mLogTag, "onQosCallbackUnregistered"); + mWrapper.mCallbackHistory.add(new CallbackType.OnQosCallbackUnregister(qosCallbackId)); + } + + @Override + protected void preventAutomaticReconnect() { + mWrapper.mPreventReconnectReceived.open(); + } + + @Override + protected void addKeepalivePacketFilter(Message msg) { + Log.i(mWrapper.mLogTag, "Add keepalive packet filter."); + } + + @Override + protected void removeKeepalivePacketFilter(Message msg) { + Log.i(mWrapper.mLogTag, "Remove keepalive packet filter."); + } + } + + public void adjustScore(int change) { + mScore += change; + mNetworkAgent.sendNetworkScore(mScore); + } + + public int getScore() { + return mScore; + } + + public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) { + mNetworkAgent.explicitlySelected(explicitlySelected, acceptUnvalidated); + } + + public void addCapability(int capability) { + mNetworkCapabilities.addCapability(capability); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void removeCapability(int capability) { + mNetworkCapabilities.removeCapability(capability); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void setUids(Set> uids) { + mNetworkCapabilities.setUids(uids); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void setSignalStrength(int signalStrength) { + mNetworkCapabilities.setSignalStrength(signalStrength); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void setNetworkSpecifier(NetworkSpecifier networkSpecifier) { + mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + + public void setNetworkCapabilities(NetworkCapabilities nc, boolean sendToConnectivityService) { + mNetworkCapabilities.set(nc); + if (sendToConnectivityService) { + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + } + + public void connect() { + if (!mConnected.compareAndSet(false /* expect */, true /* update */)) { + // compareAndSet returns false when the value couldn't be updated because it did not + // match the expected value. + fail("Test NetworkAgents can only be connected once"); + } + mNetworkAgent.markConnected(); + } + + public void suspend() { + removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + } + + public void resume() { + addCapability(NET_CAPABILITY_NOT_SUSPENDED); + } + + public void disconnect() { + mNetworkAgent.unregister(); + } + + @Override + public Network getNetwork() { + return mNetworkAgent.getNetwork(); + } + + public void expectPreventReconnectReceived(long timeoutMs) { + assertTrue(mPreventReconnectReceived.block(timeoutMs)); + } + + public void expectDisconnected(long timeoutMs) { + assertTrue(mDisconnected.block(timeoutMs)); + } + + public void sendLinkProperties(LinkProperties lp) { + mNetworkAgent.sendLinkProperties(lp); + } + + public void setStartKeepaliveEvent(int reason) { + mStartKeepaliveError = reason; + } + + public void setStopKeepaliveEvent(int reason) { + mStopKeepaliveError = reason; + } + + public void setKeepaliveResponseDelay(long delay) { + mKeepaliveResponseDelay = delay; + } + + public void setExpectedKeepaliveSlot(Integer slot) { + mExpectedKeepaliveSlot = slot; + } + + public NetworkAgent getNetworkAgent() { + return mNetworkAgent; + } + + public NetworkCapabilities getNetworkCapabilities() { + return mNetworkCapabilities; + } + + public int getLegacyType() { + return mNetworkAgentConfig.getLegacyType(); + } + + public String getExtraInfo() { + return mNetworkAgentConfig.getLegacyExtraInfo(); + } + + public @NonNull ArrayTrackRecord.ReadHead getCallbackHistory() { + return mCallbackHistory; + } + + public void waitForIdle(long timeoutMs) { + HandlerUtils.waitForIdle(mHandlerThread, timeoutMs); + } + + abstract static class CallbackType { + final int mQosCallbackId; + + protected CallbackType(final int qosCallbackId) { + mQosCallbackId = qosCallbackId; + } + + static class OnQosCallbackRegister extends CallbackType { + final QosFilter mFilter; + OnQosCallbackRegister(final int qosCallbackId, final QosFilter filter) { + super(qosCallbackId); + mFilter = filter; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OnQosCallbackRegister that = (OnQosCallbackRegister) o; + return mQosCallbackId == that.mQosCallbackId + && Objects.equals(mFilter, that.mFilter); + } + + @Override + public int hashCode() { + return Objects.hash(mQosCallbackId, mFilter); + } + } + + static class OnQosCallbackUnregister extends CallbackType { + OnQosCallbackUnregister(final int qosCallbackId) { + super(qosCallbackId); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OnQosCallbackUnregister that = (OnQosCallbackUnregister) o; + return mQosCallbackId == that.mQosCallbackId; + } + + @Override + public int hashCode() { + return Objects.hash(mQosCallbackId); + } + } + } + + public boolean isBypassableVpn() { + return mNetworkAgentConfig.isBypassableVpn(); + } +} diff --git a/tests/integration/util/com/android/server/TestNetIdManager.kt b/tests/integration/util/com/android/server/TestNetIdManager.kt new file mode 100644 index 0000000000..938a694e8b --- /dev/null +++ b/tests/integration/util/com/android/server/TestNetIdManager.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server + +import java.util.concurrent.atomic.AtomicInteger + +/** + * A [NetIdManager] that generates ID starting from [NetIdManager.MAX_NET_ID] and decreasing, rather + * than starting from [NetIdManager.MIN_NET_ID] and increasing. + * + * Useful for testing ConnectivityService, to minimize the risk of test ConnectivityService netIDs + * overlapping with netIDs used by the real ConnectivityService on the device. + * + * IDs may still overlap if many networks have been used on the device (so the "real" netIDs + * are close to MAX_NET_ID), but this is typically not the case when running unit tests. Also, there + * is no way to fully solve the overlap issue without altering ID allocation in non-test code, as + * the real ConnectivityService could start using netIds that have been used by the test in the + * past. + */ +class TestNetIdManager : NetIdManager() { + private val nextId = AtomicInteger(MAX_NET_ID) + override fun reserveNetId() = nextId.decrementAndGet() + override fun releaseNetId(id: Int) = Unit + fun peekNextNetId() = nextId.get() - 1 +} diff --git a/tests/smoketest/Android.bp b/tests/smoketest/Android.bp new file mode 100644 index 0000000000..1535f3ddcb --- /dev/null +++ b/tests/smoketest/Android.bp @@ -0,0 +1,31 @@ +// This test exists only because the jni_libs list for these tests is difficult to +// maintain: the test itself only depends on libnetworkstatsfactorytestjni, but the test +// fails to load that library unless *all* the dependencies of that library are explicitly +// listed in jni_libs. This means that whenever any of the dependencies changes the test +// starts failing and breaking presubmits in frameworks/base. We cannot easily put +// FrameworksNetTests into global presubmit because they are at times flaky, but this +// test is effectively empty beyond validating that the libraries load correctly, and +// thus should be stable enough to put in global presubmit. +// +// TODO: remove this hack when there is a better solution for jni_libs that includes +// dependent libraries. +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "FrameworksNetSmokeTests", + defaults: ["FrameworksNetTests-jni-defaults"], + srcs: ["java/SmokeTest.java"], + test_suites: ["device-tests"], + static_libs: [ + "androidx.test.rules", + "mockito-target-minus-junit4", + "services.core", + ], +} diff --git a/tests/smoketest/AndroidManifest.xml b/tests/smoketest/AndroidManifest.xml new file mode 100644 index 0000000000..f1b9febb9f --- /dev/null +++ b/tests/smoketest/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/tests/smoketest/AndroidTest.xml b/tests/smoketest/AndroidTest.xml new file mode 100644 index 0000000000..ac366e4ac5 --- /dev/null +++ b/tests/smoketest/AndroidTest.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/tests/smoketest/java/SmokeTest.java b/tests/smoketest/java/SmokeTest.java new file mode 100644 index 0000000000..7d6655fde1 --- /dev/null +++ b/tests/smoketest/java/SmokeTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class SmokeTest { + + @Test + public void testLoadJni() { + System.loadLibrary("networkstatsfactorytestjni"); + assertEquals(0, 0x00); + } +} diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp new file mode 100644 index 0000000000..e1a424f214 --- /dev/null +++ b/tests/unit/Android.bp @@ -0,0 +1,87 @@ +//######################################################################## +// Build FrameworksNetTests package +//######################################################################## +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_defaults { + name: "FrameworksNetTests-jni-defaults", + jni_libs: [ + "ld-android", + "libbacktrace", + "libbase", + "libbinder", + "libbpf", + "libbpf_android", + "libc++", + "libcgrouprc", + "libcrypto", + "libcutils", + "libdl_android", + "libhidl-gen-utils", + "libhidlbase", + "libjsoncpp", + "liblog", + "liblzma", + "libnativehelper", + "libnetdbpf", + "libnetdutils", + "libnetworkstatsfactorytestjni", + "libpackagelistparser", + "libpcre2", + "libprocessgroup", + "libselinux", + "libtinyxml2", + "libui", + "libunwindstack", + "libutils", + "libutilscallstack", + "libvndksupport", + "libziparchive", + "libz", + "netd_aidl_interface-V5-cpp", + ], +} + +android_test { + name: "FrameworksNetTests", + defaults: ["FrameworksNetTests-jni-defaults"], + srcs: [ + "java/**/*.java", + "java/**/*.kt", + ], + platform_apis: true, + test_suites: ["device-tests"], + certificate: "platform", + jarjar_rules: "jarjar-rules.txt", + static_libs: [ + "androidx.test.rules", + "bouncycastle-repackaged-unbundled", + "FrameworksNetCommonTests", + "frameworks-base-testutils", + "frameworks-net-integration-testutils", + "framework-protos", + "mockito-target-minus-junit4", + "net-tests-utils", + "platform-test-annotations", + "service-connectivity-pre-jarjar", + "services.core", + "services.net", + ], + libs: [ + "android.net.ipsec.ike.stubs.module_lib", + "android.test.runner", + "android.test.base", + "android.test.mock", + "ServiceConnectivityResources", + ], + jni_libs: [ + "libservice-connectivity", + ], +} diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml new file mode 100644 index 0000000000..d08b2f8d40 --- /dev/null +++ b/tests/unit/AndroidManifest.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/unit/AndroidTest.xml b/tests/unit/AndroidTest.xml new file mode 100644 index 0000000000..939ae493b2 --- /dev/null +++ b/tests/unit/AndroidTest.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/tests/unit/jarjar-rules.txt b/tests/unit/jarjar-rules.txt new file mode 100644 index 0000000000..ca8867206d --- /dev/null +++ b/tests/unit/jarjar-rules.txt @@ -0,0 +1,2 @@ +# Module library in frameworks/libs/net +rule com.android.net.module.util.** android.net.frameworktests.util.@1 diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java new file mode 100644 index 0000000000..899295a019 --- /dev/null +++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2018 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.app.usage; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.ConnectivityManager; +import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; +import android.net.NetworkStats.Entry; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.os.RemoteException; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsManagerTest { + + private @Mock INetworkStatsService mService; + private @Mock INetworkStatsSession mStatsSession; + + private NetworkStatsManager mManager; + + // TODO: change to NetworkTemplate.MATCH_MOBILE once internal constant rename is merged to aosp. + private static final int MATCH_MOBILE_ALL = 1; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mManager = new NetworkStatsManager(InstrumentationRegistry.getContext(), mService); + } + + @Test + public void testQueryDetails() throws RemoteException { + final String subscriberId = "subid"; + final long startTime = 1; + final long endTime = 100; + final int uid1 = 10001; + final int uid2 = 10002; + final int uid3 = 10003; + + Entry uid1Entry1 = new Entry("if1", uid1, + android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE, + 100, 10, 200, 20, 0); + + Entry uid1Entry2 = new Entry( + "if2", uid1, + android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE, + 100, 10, 200, 20, 0); + + Entry uid2Entry1 = new Entry("if1", uid2, + android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE, + 150, 10, 250, 20, 0); + + Entry uid2Entry2 = new Entry( + "if2", uid2, + android.net.NetworkStats.SET_DEFAULT, android.net.NetworkStats.TAG_NONE, + 150, 10, 250, 20, 0); + + NetworkStatsHistory history1 = new NetworkStatsHistory(10, 2); + history1.recordData(10, 20, uid1Entry1); + history1.recordData(20, 30, uid1Entry2); + + NetworkStatsHistory history2 = new NetworkStatsHistory(10, 2); + history1.recordData(30, 40, uid2Entry1); + history1.recordData(35, 45, uid2Entry2); + + + when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession); + when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2, uid3 }); + + when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class), + eq(uid1), eq(android.net.NetworkStats.SET_ALL), + eq(android.net.NetworkStats.TAG_NONE), + eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime))) + .then((InvocationOnMock inv) -> { + NetworkTemplate template = inv.getArgument(0); + assertEquals(MATCH_MOBILE_ALL, template.getMatchRule()); + assertEquals(subscriberId, template.getSubscriberId()); + return history1; + }); + + when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class), + eq(uid2), eq(android.net.NetworkStats.SET_ALL), + eq(android.net.NetworkStats.TAG_NONE), + eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime))) + .then((InvocationOnMock inv) -> { + NetworkTemplate template = inv.getArgument(0); + assertEquals(MATCH_MOBILE_ALL, template.getMatchRule()); + assertEquals(subscriberId, template.getSubscriberId()); + return history2; + }); + + + NetworkStats stats = mManager.queryDetails( + ConnectivityManager.TYPE_MOBILE, subscriberId, startTime, endTime); + + NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + + // First 2 buckets exactly match entry timings + assertTrue(stats.getNextBucket(bucket)); + assertEquals(10, bucket.getStartTimeStamp()); + assertEquals(20, bucket.getEndTimeStamp()); + assertBucketMatches(uid1Entry1, bucket); + + assertTrue(stats.getNextBucket(bucket)); + assertEquals(20, bucket.getStartTimeStamp()); + assertEquals(30, bucket.getEndTimeStamp()); + assertBucketMatches(uid1Entry2, bucket); + + // 30 -> 40: contains uid2Entry1 and half of uid2Entry2 + assertTrue(stats.getNextBucket(bucket)); + assertEquals(30, bucket.getStartTimeStamp()); + assertEquals(40, bucket.getEndTimeStamp()); + assertEquals(225, bucket.getRxBytes()); + assertEquals(15, bucket.getRxPackets()); + assertEquals(375, bucket.getTxBytes()); + assertEquals(30, bucket.getTxPackets()); + + // 40 -> 50: contains half of uid2Entry2 + assertTrue(stats.getNextBucket(bucket)); + assertEquals(40, bucket.getStartTimeStamp()); + assertEquals(50, bucket.getEndTimeStamp()); + assertEquals(75, bucket.getRxBytes()); + assertEquals(5, bucket.getRxPackets()); + assertEquals(125, bucket.getTxBytes()); + assertEquals(10, bucket.getTxPackets()); + + assertFalse(stats.hasNextBucket()); + } + + @Test + public void testQueryDetails_NoSubscriberId() throws RemoteException { + final long startTime = 1; + final long endTime = 100; + final int uid1 = 10001; + final int uid2 = 10002; + + when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession); + when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2 }); + + NetworkStats stats = mManager.queryDetails( + ConnectivityManager.TYPE_MOBILE, null, startTime, endTime); + + when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class), + anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyLong())) + .thenReturn(new NetworkStatsHistory(10, 0)); + + verify(mStatsSession, times(1)).getHistoryIntervalForUid( + argThat((NetworkTemplate t) -> + // No subscriberId: MATCH_MOBILE_WILDCARD template + t.getMatchRule() == NetworkTemplate.MATCH_MOBILE_WILDCARD), + eq(uid1), eq(android.net.NetworkStats.SET_ALL), + eq(android.net.NetworkStats.TAG_NONE), + eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime)); + + verify(mStatsSession, times(1)).getHistoryIntervalForUid( + argThat((NetworkTemplate t) -> + // No subscriberId: MATCH_MOBILE_WILDCARD template + t.getMatchRule() == NetworkTemplate.MATCH_MOBILE_WILDCARD), + eq(uid2), eq(android.net.NetworkStats.SET_ALL), + eq(android.net.NetworkStats.TAG_NONE), + eq(NetworkStatsHistory.FIELD_ALL), eq(startTime), eq(endTime)); + + assertFalse(stats.hasNextBucket()); + } + + private void assertBucketMatches(Entry expected, NetworkStats.Bucket actual) { + assertEquals(expected.uid, actual.getUid()); + assertEquals(expected.rxBytes, actual.getRxBytes()); + assertEquals(expected.rxPackets, actual.getRxPackets()); + assertEquals(expected.txBytes, actual.getTxBytes()); + assertEquals(expected.txPackets, actual.getTxPackets()); + } +} diff --git a/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java new file mode 100644 index 0000000000..06e9405a6a --- /dev/null +++ b/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -0,0 +1,384 @@ +/* + * 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 android.net; + +import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsBinder; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport; + +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.content.Context; +import android.os.PersistableBundle; + +import androidx.test.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; + +import java.util.concurrent.Executor; + +@RunWith(JUnit4.class) +public class ConnectivityDiagnosticsManagerTest { + private static final int NET_ID = 1; + private static final int DETECTION_METHOD = 2; + private static final long TIMESTAMP = 10L; + private static final String INTERFACE_NAME = "interface"; + private static final String BUNDLE_KEY = "key"; + private static final String BUNDLE_VALUE = "value"; + + private static final Executor INLINE_EXECUTOR = x -> x.run(); + + @Mock private IConnectivityManager mService; + @Mock private ConnectivityDiagnosticsCallback mCb; + + private Context mContext; + private ConnectivityDiagnosticsBinder mBinder; + private ConnectivityDiagnosticsManager mManager; + + private String mPackageName; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getContext(); + + mService = mock(IConnectivityManager.class); + mCb = mock(ConnectivityDiagnosticsCallback.class); + + mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR); + mManager = new ConnectivityDiagnosticsManager(mContext, mService); + + mPackageName = mContext.getOpPackageName(); + } + + @After + public void tearDown() { + // clear ConnectivityDiagnosticsManager callbacks map + ConnectivityDiagnosticsManager.sCallbacks.clear(); + } + + private ConnectivityReport createSampleConnectivityReport() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + return new ConnectivityReport( + new Network(NET_ID), TIMESTAMP, linkProperties, networkCapabilities, bundle); + } + + private ConnectivityReport createDefaultConnectivityReport() { + return new ConnectivityReport( + new Network(0), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY); + } + + @Test + public void testPersistableBundleEquals() { + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + null, PersistableBundle.EMPTY)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, null)); + assertTrue( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, PersistableBundle.EMPTY)); + + final PersistableBundle a = new PersistableBundle(); + a.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle b = new PersistableBundle(); + b.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle c = new PersistableBundle(); + c.putString(BUNDLE_KEY, null); + + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(PersistableBundle.EMPTY, a)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(a, PersistableBundle.EMPTY)); + + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(a, b)); + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(b, a)); + + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(a, c)); + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(c, a)); + } + + @Test + public void testConnectivityReportEquals() { + final ConnectivityReport defaultReport = createDefaultConnectivityReport(); + final ConnectivityReport sampleReport = createSampleConnectivityReport(); + assertEquals(sampleReport, createSampleConnectivityReport()); + assertEquals(defaultReport, createDefaultConnectivityReport()); + + final LinkProperties linkProperties = sampleReport.getLinkProperties(); + final NetworkCapabilities networkCapabilities = sampleReport.getNetworkCapabilities(); + final PersistableBundle bundle = sampleReport.getAdditionalInfo(); + + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(NET_ID), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + 0L, + linkProperties, + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + networkCapabilities, + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + bundle)); + } + + @Test + public void testConnectivityReportParcelUnparcel() { + assertParcelSane(createSampleConnectivityReport(), 5); + } + + private DataStallReport createSampleDataStallReport() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + return new DataStallReport( + new Network(NET_ID), + TIMESTAMP, + DETECTION_METHOD, + linkProperties, + networkCapabilities, + bundle); + } + + private DataStallReport createDefaultDataStallReport() { + return new DataStallReport( + new Network(0), + 0L, + 0, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY); + } + + @Test + public void testDataStallReportEquals() { + final DataStallReport defaultReport = createDefaultDataStallReport(); + final DataStallReport sampleReport = createSampleDataStallReport(); + assertEquals(sampleReport, createSampleDataStallReport()); + assertEquals(defaultReport, createDefaultDataStallReport()); + + final LinkProperties linkProperties = sampleReport.getLinkProperties(); + final NetworkCapabilities networkCapabilities = sampleReport.getNetworkCapabilities(); + final PersistableBundle bundle = sampleReport.getStallDetails(); + + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(NET_ID), + 0L, + 0, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + TIMESTAMP, + 0, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + 0L, + DETECTION_METHOD, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + 0L, + 0, + linkProperties, + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + 0L, + 0, + new LinkProperties(), + networkCapabilities, + PersistableBundle.EMPTY)); + assertNotEquals( + defaultReport, + new DataStallReport( + new Network(0), + 0L, + 0, + new LinkProperties(), + new NetworkCapabilities(), + bundle)); + } + + @Test + public void testDataStallReportParcelUnparcel() { + assertParcelSane(createSampleDataStallReport(), 6); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReportAvailable() { + mBinder.onConnectivityReportAvailable(createSampleConnectivityReport()); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onConnectivityReportAvailable(eq(createSampleConnectivityReport())); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() { + mBinder.onDataStallSuspected(createSampleDataStallReport()); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onDataStallSuspected(eq(createSampleDataStallReport())); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnNetworkConnectivityReported() { + final Network n = new Network(NET_ID); + final boolean connectivity = true; + + mBinder.onNetworkConnectivityReported(n, connectivity); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onNetworkConnectivityReported(eq(n), eq(connectivity)); + } + + @Test + public void testRegisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + verify(mService).registerConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName)); + assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + } + + @Test + public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + try { + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + fail("Duplicate callback registration should fail"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testUnregisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + + mManager.unregisterConnectivityDiagnosticsCallback(mCb); + + verify(mService).unregisterConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class)); + assertFalse(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + + // verify that re-registering is successful + mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); + verify(mService, times(2)).registerConnectivityDiagnosticsCallback( + any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName)); + assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); + } + + @Test + public void testUnregisterUnknownConnectivityDiagnosticsCallback() throws Exception { + mManager.unregisterConnectivityDiagnosticsCallback(mCb); + + verifyNoMoreInteractions(mService); + } +} diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java new file mode 100644 index 0000000000..591e0cc350 --- /dev/null +++ b/tests/unit/java/android/net/ConnectivityManagerTest.java @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2017 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; + +import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; +import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; +import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; +import static android.net.NetworkRequest.Type.REQUEST; +import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.net.ConnectivityManager.NetworkCallback; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Process; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConnectivityManagerTest { + + @Mock Context mCtx; + @Mock IConnectivityManager mService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + static NetworkCapabilities verifyNetworkCapabilities( + int legacyType, int transportType, int... capabilities) { + final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType); + assertNotNull(nc); + assertTrue(nc.hasTransport(transportType)); + for (int capability : capabilities) { + assertTrue(nc.hasCapability(capability)); + } + + return nc; + } + + static void verifyUnrestrictedNetworkCapabilities(int legacyType, int transportType) { + verifyNetworkCapabilities( + legacyType, + transportType, + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_NOT_RESTRICTED, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_TRUSTED); + } + + static void verifyRestrictedMobileNetworkCapabilities(int legacyType, int capability) { + final NetworkCapabilities nc = verifyNetworkCapabilities( + legacyType, + TRANSPORT_CELLULAR, + capability, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_TRUSTED); + + assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET)); + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + } + + @Test + public void testNetworkCapabilitiesForTypeMobile() { + verifyUnrestrictedNetworkCapabilities( + ConnectivityManager.TYPE_MOBILE, TRANSPORT_CELLULAR); + } + + @Test + public void testNetworkCapabilitiesForTypeMobileCbs() { + verifyRestrictedMobileNetworkCapabilities( + ConnectivityManager.TYPE_MOBILE_CBS, NET_CAPABILITY_CBS); + } + + @Test + public void testNetworkCapabilitiesForTypeMobileDun() { + verifyRestrictedMobileNetworkCapabilities( + ConnectivityManager.TYPE_MOBILE_DUN, NET_CAPABILITY_DUN); + } + + @Test + public void testNetworkCapabilitiesForTypeMobileFota() { + verifyRestrictedMobileNetworkCapabilities( + ConnectivityManager.TYPE_MOBILE_FOTA, NET_CAPABILITY_FOTA); + } + + @Test + public void testNetworkCapabilitiesForTypeMobileHipri() { + verifyUnrestrictedNetworkCapabilities( + ConnectivityManager.TYPE_MOBILE_HIPRI, TRANSPORT_CELLULAR); + } + + @Test + public void testNetworkCapabilitiesForTypeMobileIms() { + verifyRestrictedMobileNetworkCapabilities( + ConnectivityManager.TYPE_MOBILE_IMS, NET_CAPABILITY_IMS); + } + + @Test + public void testNetworkCapabilitiesForTypeMobileMms() { + final NetworkCapabilities nc = verifyNetworkCapabilities( + ConnectivityManager.TYPE_MOBILE_MMS, + TRANSPORT_CELLULAR, + NET_CAPABILITY_MMS, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_TRUSTED); + + assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET)); + } + + @Test + public void testNetworkCapabilitiesForTypeMobileSupl() { + final NetworkCapabilities nc = verifyNetworkCapabilities( + ConnectivityManager.TYPE_MOBILE_SUPL, + TRANSPORT_CELLULAR, + NET_CAPABILITY_SUPL, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_TRUSTED); + + assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET)); + } + + @Test + public void testNetworkCapabilitiesForTypeWifi() { + verifyUnrestrictedNetworkCapabilities( + ConnectivityManager.TYPE_WIFI, TRANSPORT_WIFI); + } + + @Test + public void testNetworkCapabilitiesForTypeWifiP2p() { + final NetworkCapabilities nc = verifyNetworkCapabilities( + ConnectivityManager.TYPE_WIFI_P2P, + TRANSPORT_WIFI, + NET_CAPABILITY_NOT_RESTRICTED, NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_TRUSTED, NET_CAPABILITY_WIFI_P2P); + + assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET)); + } + + @Test + public void testNetworkCapabilitiesForTypeBluetooth() { + verifyUnrestrictedNetworkCapabilities( + ConnectivityManager.TYPE_BLUETOOTH, TRANSPORT_BLUETOOTH); + } + + @Test + public void testNetworkCapabilitiesForTypeEthernet() { + verifyUnrestrictedNetworkCapabilities( + ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET); + } + + @Test + public void testCallbackRelease() throws Exception { + ConnectivityManager manager = new ConnectivityManager(mCtx, mService); + NetworkRequest request = makeRequest(1); + NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class); + Handler handler = new Handler(Looper.getMainLooper()); + ArgumentCaptor captor = ArgumentCaptor.forClass(Messenger.class); + + // register callback + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(request); + manager.requestNetwork(request, callback, handler); + + // callback triggers + captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE)); + verify(callback, timeout(500).times(1)).onAvailable(any(Network.class), + any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean()); + + // unregister callback + manager.unregisterNetworkCallback(callback); + verify(mService, times(1)).releaseNetworkRequest(request); + + // callback does not trigger anymore. + captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_LOSING)); + verify(callback, timeout(500).times(0)).onLosing(any(), anyInt()); + } + + @Test + public void testCallbackRecycling() throws Exception { + ConnectivityManager manager = new ConnectivityManager(mCtx, mService); + NetworkRequest req1 = makeRequest(1); + NetworkRequest req2 = makeRequest(2); + NetworkCallback callback = mock(ConnectivityManager.NetworkCallback.class); + Handler handler = new Handler(Looper.getMainLooper()); + ArgumentCaptor captor = ArgumentCaptor.forClass(Messenger.class); + + // register callback + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req1); + manager.requestNetwork(req1, callback, handler); + + // callback triggers + captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE)); + verify(callback, timeout(100).times(1)).onAvailable(any(Network.class), + any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean()); + + // unregister callback + manager.unregisterNetworkCallback(callback); + verify(mService, times(1)).releaseNetworkRequest(req1); + + // callback does not trigger anymore. + captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_LOSING)); + verify(callback, timeout(100).times(0)).onLosing(any(), anyInt()); + + // callback can be registered again + when(mService.requestNetwork(anyInt(), any(), anyInt(), captor.capture(), anyInt(), any(), + anyInt(), anyInt(), any(), nullable(String.class))).thenReturn(req2); + manager.requestNetwork(req2, callback, handler); + + // callback triggers + captor.getValue().send(makeMessage(req2, ConnectivityManager.CALLBACK_LOST)); + verify(callback, timeout(100).times(1)).onLost(any()); + + // unregister callback + manager.unregisterNetworkCallback(callback); + verify(mService, times(1)).releaseNetworkRequest(req2); + } + + // TODO: turn on this test when request callback 1:1 mapping is enforced + //@Test + private void noDoubleCallbackRegistration() throws Exception { + ConnectivityManager manager = new ConnectivityManager(mCtx, mService); + NetworkRequest request = makeRequest(1); + NetworkCallback callback = new ConnectivityManager.NetworkCallback(); + ApplicationInfo info = new ApplicationInfo(); + // TODO: update version when starting to enforce 1:1 mapping + info.targetSdkVersion = VERSION_CODES.N_MR1 + 1; + + when(mCtx.getApplicationInfo()).thenReturn(info); + when(mService.requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), anyInt(), + anyInt(), any(), nullable(String.class))).thenReturn(request); + + Handler handler = new Handler(Looper.getMainLooper()); + manager.requestNetwork(request, callback, handler); + + // callback is already registered, reregistration should fail. + Class wantException = IllegalArgumentException.class; + expectThrowable(() -> manager.requestNetwork(request, callback), wantException); + + manager.unregisterNetworkCallback(callback); + verify(mService, times(1)).releaseNetworkRequest(request); + + // unregistering the callback should make it registrable again. + manager.requestNetwork(request, callback); + } + + @Test + public void testArgumentValidation() throws Exception { + ConnectivityManager manager = new ConnectivityManager(mCtx, mService); + + NetworkRequest request = mock(NetworkRequest.class); + NetworkCallback callback = mock(NetworkCallback.class); + Handler handler = mock(Handler.class); + NetworkCallback nullCallback = null; + PendingIntent nullIntent = null; + + mustFail(() -> { manager.requestNetwork(null, callback); }); + mustFail(() -> { manager.requestNetwork(request, nullCallback); }); + mustFail(() -> { manager.requestNetwork(request, callback, null); }); + mustFail(() -> { manager.requestNetwork(request, callback, -1); }); + mustFail(() -> { manager.requestNetwork(request, nullIntent); }); + + mustFail(() -> { manager.registerNetworkCallback(null, callback, handler); }); + mustFail(() -> { manager.registerNetworkCallback(request, null, handler); }); + mustFail(() -> { manager.registerNetworkCallback(request, callback, null); }); + mustFail(() -> { manager.registerNetworkCallback(request, nullIntent); }); + + mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); }); + mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); }); + + mustFail(() -> { manager.registerSystemDefaultNetworkCallback(null, handler); }); + mustFail(() -> { manager.registerSystemDefaultNetworkCallback(callback, null); }); + + mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); }); + mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); }); + mustFail(() -> { manager.releaseNetworkRequest(nullIntent); }); + } + + static void mustFail(Runnable fn) { + try { + fn.run(); + fail(); + } catch (Exception expected) { + } + } + + @Test + public void testRequestType() throws Exception { + final String testPkgName = "MyPackage"; + final String testAttributionTag = "MyTag"; + final ConnectivityManager manager = new ConnectivityManager(mCtx, mService); + when(mCtx.getOpPackageName()).thenReturn(testPkgName); + when(mCtx.getAttributionTag()).thenReturn(testAttributionTag); + final NetworkRequest request = makeRequest(1); + final NetworkCallback callback = new ConnectivityManager.NetworkCallback(); + + manager.requestNetwork(request, callback); + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), + eq(REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), + eq(testPkgName), eq(testAttributionTag)); + reset(mService); + + // Verify that register network callback does not calls requestNetwork at all. + manager.registerNetworkCallback(request, callback); + verify(mService, never()).requestNetwork(anyInt(), any(), anyInt(), any(), anyInt(), any(), + anyInt(), anyInt(), any(), any()); + verify(mService).listenForNetwork(eq(request.networkCapabilities), any(), any(), anyInt(), + eq(testPkgName), eq(testAttributionTag)); + reset(mService); + + Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + + manager.registerDefaultNetworkCallback(callback); + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null), + eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), + eq(testPkgName), eq(testAttributionTag)); + reset(mService); + + manager.registerDefaultNetworkCallbackForUid(42, callback, handler); + verify(mService).requestNetwork(eq(42), eq(null), + eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), + eq(testPkgName), eq(testAttributionTag)); + + manager.requestBackgroundNetwork(request, callback, handler); + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities), + eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), + eq(testPkgName), eq(testAttributionTag)); + reset(mService); + + manager.registerSystemDefaultNetworkCallback(callback, handler); + verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(null), + eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(), + eq(testPkgName), eq(testAttributionTag)); + reset(mService); + } + + static Message makeMessage(NetworkRequest req, int messageType) { + Bundle bundle = new Bundle(); + bundle.putParcelable(NetworkRequest.class.getSimpleName(), req); + // Pass default objects as we don't care which get passed here + bundle.putParcelable(Network.class.getSimpleName(), new Network(1)); + bundle.putParcelable(NetworkCapabilities.class.getSimpleName(), new NetworkCapabilities()); + bundle.putParcelable(LinkProperties.class.getSimpleName(), new LinkProperties()); + Message msg = Message.obtain(); + msg.what = messageType; + msg.setData(bundle); + return msg; + } + + static NetworkRequest makeRequest(int requestId) { + NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); + return new NetworkRequest(request.networkCapabilities, ConnectivityManager.TYPE_NONE, + requestId, NetworkRequest.Type.NONE); + } + + static void expectThrowable(Runnable block, Class throwableType) { + try { + block.run(); + } catch (Throwable t) { + if (t.getClass().equals(throwableType)) { + return; + } + fail("expected exception of type " + throwableType + ", but was " + t.getClass()); + } + fail("expected exception of type " + throwableType); + } +} diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java new file mode 100644 index 0000000000..1abd39a32b --- /dev/null +++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java @@ -0,0 +1,439 @@ +/* + * 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; + +import static org.junit.Assert.assertArrayEquals; +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 android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.net.VpnProfile; +import com.android.net.module.util.ProxyUtils; +import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.security.auth.x500.X500Principal; + +/** Unit tests for {@link Ikev2VpnProfile.Builder}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class Ikev2VpnProfileTest { + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final String USERNAME_STRING = "username"; + private static final String PASSWORD_STRING = "pa55w0rd"; + private static final String EXCL_LIST = "exclList"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); + private static final int TEST_MTU = 1300; + + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return "fooPackage"; + } + }; + private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy( + SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST)); + + private X509Certificate mUserCert; + private X509Certificate mServerRootCa; + private PrivateKey mPrivateKey; + + @Before + public void setUp() throws Exception { + mServerRootCa = generateRandomCertAndKeyPair().cert; + + final CertificateAndKey userCertKey = generateRandomCertAndKeyPair(); + mUserCert = userCertKey.cert; + mPrivateKey = userCertKey.key; + } + + private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() { + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING); + + builder.setBypassable(true); + builder.setProxy(mProxy); + builder.setMaxMtu(TEST_MTU); + builder.setMetered(true); + + return builder; + } + + @Test + public void testBuildValidProfileWithOptions() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + // Check non-auth parameters correctly stored + assertEquals(SERVER_ADDR_STRING, profile.getServerAddr()); + assertEquals(IDENTITY_STRING, profile.getUserIdentity()); + assertEquals(mProxy, profile.getProxyInfo()); + assertTrue(profile.isBypassable()); + assertTrue(profile.isMetered()); + assertEquals(TEST_MTU, profile.getMaxMtu()); + assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms()); + } + + @Test + public void testBuildUsernamePasswordProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(USERNAME_STRING, profile.getUsername()); + assertEquals(PASSWORD_STRING, profile.getPassword()); + assertEquals(mServerRootCa, profile.getServerRootCaCert()); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildDigitalSignatureProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(profile.getUserCert(), mUserCert); + assertEquals(mPrivateKey, profile.getRsaPrivateKey()); + assertEquals(profile.getServerRootCaCert(), mServerRootCa); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + } + + @Test + public void testBuildPresharedKeyProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertArrayEquals(PSK_BYTES, profile.getPresharedKey()); + + assertNull(profile.getServerRootCaCert()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildWithAllowedAlgorithmsAead() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + + List allowedAlgorithms = Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM); + builder.setAllowedAlgorithms(allowedAlgorithms); + + final Ikev2VpnProfile profile = builder.build(); + assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms()); + } + + @Test + public void testBuildWithAllowedAlgorithmsNormal() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + builder.setAuthPsk(PSK_BYTES); + + List allowedAlgorithms = + Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA512, IpSecAlgorithm.CRYPT_AES_CBC); + builder.setAllowedAlgorithms(allowedAlgorithms); + + final Ikev2VpnProfile profile = builder.build(); + assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms()); + } + + @Test + public void testSetAllowedAlgorithmsEmptyList() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setAllowedAlgorithms(new ArrayList<>()); + fail("Expected exception due to no valid algorithm set"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetAllowedAlgorithmsInvalidList() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + List allowedAlgorithms = new ArrayList<>(); + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256)); + fail("Expected exception due to missing encryption"); + } catch (IllegalArgumentException expected) { + } + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC)); + fail("Expected exception due to missing authentication"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + List allowedAlgorithms = new ArrayList<>(); + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5)); + fail("Expected exception due to insecure algorithm"); + } catch (IllegalArgumentException expected) { + } + + try { + builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1)); + fail("Expected exception due to insecure algorithm"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBuildNoAuthMethodSet() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.build(); + fail("Expected exception due to lack of auth method"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBuildInvalidMtu() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setMaxMtu(500); + fail("Expected exception due to too-small MTU"); + } catch (IllegalArgumentException expected) { + } + } + + private void verifyVpnProfileCommon(VpnProfile profile) { + assertEquals(SERVER_ADDR_STRING, profile.server); + assertEquals(IDENTITY_STRING, profile.ipsecIdentifier); + assertEquals(mProxy, profile.proxy); + assertTrue(profile.isBypassable); + assertTrue(profile.isMetered); + assertEquals(TEST_MTU, profile.maxMtu); + } + + @Test + public void testPskConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecCaCert); + } + + @Test + public void testUsernamePasswordConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(USERNAME_STRING, profile.username); + assertEquals(PASSWORD_STRING, profile.password); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecSecret); + } + + @Test + public void testRsaConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE + + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()); + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert); + assertEquals( + expectedSecret, + profile.ipsecSecret); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + } + + @Test + public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + assertNull(result.getServerRootCaCert()); + } + + @Test + public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.ipsecSecret = new String(PSK_BYTES); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getPresharedKey()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + } + + @Test + public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getPresharedKey()); + } + + @Test + public void testPskConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testUsernamePasswordConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testRsaConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + private static class CertificateAndKey { + public final X509Certificate cert; + public final PrivateKey key; + + CertificateAndKey(X509Certificate cert, PrivateKey key) { + this.cert = cert; + this.key = key; + } + } + + private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception { + final Date validityBeginDate = + new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L)); + final Date validityEndDate = + new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L)); + + // Generate a keypair + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(512); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + final X500Principal dnName = new X500Principal("CN=test.android.com"); + final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); + certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); + certGen.setSubjectDN(dnName); + certGen.setIssuerDN(dnName); + certGen.setNotBefore(validityBeginDate); + certGen.setNotAfter(validityEndDate); + certGen.setPublicKey(keyPair.getPublic()); + certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL"); + return new CertificateAndKey(cert, keyPair.getPrivate()); + } +} diff --git a/tests/unit/java/android/net/IpMemoryStoreTest.java b/tests/unit/java/android/net/IpMemoryStoreTest.java new file mode 100644 index 0000000000..0b13800bc5 --- /dev/null +++ b/tests/unit/java/android/net/IpMemoryStoreTest.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2018 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.net.ipmemorystore.Blob; +import android.net.ipmemorystore.IOnStatusListener; +import android.net.ipmemorystore.NetworkAttributes; +import android.net.ipmemorystore.NetworkAttributesParcelable; +import android.net.ipmemorystore.Status; +import android.net.networkstack.ModuleNetworkStackClient; +import android.os.RemoteException; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.net.UnknownHostException; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpMemoryStoreTest { + private static final String TAG = IpMemoryStoreTest.class.getSimpleName(); + private static final String TEST_CLIENT_ID = "testClientId"; + private static final String TEST_DATA_NAME = "testData"; + private static final String TEST_OTHER_DATA_NAME = TEST_DATA_NAME + "Other"; + private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12, + -128, 0, 89, 112, 91, -34 }; + private static final NetworkAttributes TEST_NETWORK_ATTRIBUTES = buildTestNetworkAttributes( + "hint", 219); + + @Mock + Context mMockContext; + @Mock + ModuleNetworkStackClient mModuleNetworkStackClient; + @Mock + IIpMemoryStore mMockService; + @Mock + IOnStatusListener mIOnStatusListener; + IpMemoryStore mStore; + + @Captor + ArgumentCaptor mCbCaptor; + @Captor + ArgumentCaptor mNapCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + private void startIpMemoryStore(boolean supplyService) { + if (supplyService) { + doAnswer(invocation -> { + ((IIpMemoryStoreCallbacks) invocation.getArgument(0)) + .onIpMemoryStoreFetched(mMockService); + return null; + }).when(mModuleNetworkStackClient).fetchIpMemoryStore(any()); + } else { + doNothing().when(mModuleNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture()); + } + mStore = new IpMemoryStore(mMockContext) { + @Override + protected ModuleNetworkStackClient getModuleNetworkStackClient(Context ctx) { + return mModuleNetworkStackClient; + } + }; + } + + private static NetworkAttributes buildTestNetworkAttributes(String hint, int mtu) { + return new NetworkAttributes.Builder() + .setCluster(hint) + .setMtu(mtu) + .build(); + } + + @Test + public void testNetworkAttributes() throws Exception { + startIpMemoryStore(true /* supplyService */); + final String l2Key = "fakeKey"; + + mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES, + status -> assertTrue("Store not successful : " + status.resultCode, + status.isSuccess())); + verify(mMockService, times(1)).storeNetworkAttributes(eq(l2Key), + mNapCaptor.capture(), any()); + assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue())); + + mStore.retrieveNetworkAttributes(l2Key, + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(TEST_NETWORK_ATTRIBUTES, attr); + }); + + verify(mMockService, times(1)).retrieveNetworkAttributes(eq(l2Key), any()); + } + + @Test + public void testPrivateData() throws RemoteException { + startIpMemoryStore(true /* supplyService */); + final Blob b = new Blob(); + b.data = TEST_BLOB_DATA; + final String l2Key = "fakeKey"; + + mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b, + status -> { + assertTrue("Store not successful : " + status.resultCode, status.isSuccess()); + }); + verify(mMockService, times(1)).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME), + eq(b), any()); + + mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME, + (status, key, name, data) -> { + assertTrue("Retrieve blob status not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(name, TEST_DATA_NAME); + assertTrue(Arrays.equals(b.data, data.data)); + }); + verify(mMockService, times(1)).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID), + eq(TEST_OTHER_DATA_NAME), any()); + } + + @Test + public void testFindL2Key() + throws UnknownHostException, RemoteException, Exception { + startIpMemoryStore(true /* supplyService */); + final String l2Key = "fakeKey"; + + mStore.findL2Key(TEST_NETWORK_ATTRIBUTES, + (status, key) -> { + assertTrue("Retrieve network sameness not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + }); + verify(mMockService, times(1)).findL2Key(mNapCaptor.capture(), any()); + assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue())); + } + + @Test + public void testIsSameNetwork() throws UnknownHostException, RemoteException { + startIpMemoryStore(true /* supplyService */); + final String l2Key1 = "fakeKey1"; + final String l2Key2 = "fakeKey2"; + + mStore.isSameNetwork(l2Key1, l2Key2, + (status, answer) -> { + assertFalse("Retrieve network sameness suspiciously successful : " + + status.resultCode, status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + assertNull(answer); + }); + verify(mMockService, times(1)).isSameNetwork(eq(l2Key1), eq(l2Key2), any()); + } + + @Test + public void testEnqueuedIpMsRequests() throws Exception { + startIpMemoryStore(false /* supplyService */); + + final Blob b = new Blob(); + b.data = TEST_BLOB_DATA; + final String l2Key = "fakeKey"; + + // enqueue multiple ipms requests + mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES, + status -> assertTrue("Store not successful : " + status.resultCode, + status.isSuccess())); + mStore.retrieveNetworkAttributes(l2Key, + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(TEST_NETWORK_ATTRIBUTES, attr); + }); + mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b, + status -> assertTrue("Store not successful : " + status.resultCode, + status.isSuccess())); + mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME, + (status, key, name, data) -> { + assertTrue("Retrieve blob status not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(name, TEST_DATA_NAME); + assertTrue(Arrays.equals(b.data, data.data)); + }); + + // get ipms service ready + mCbCaptor.getValue().onIpMemoryStoreFetched(mMockService); + + InOrder inOrder = inOrder(mMockService); + + inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any()); + inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any()); + inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME), + eq(b), any()); + inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID), + eq(TEST_OTHER_DATA_NAME), any()); + assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue())); + } + + @Test + public void testEnqueuedIpMsRequestsWithException() throws Exception { + startIpMemoryStore(true /* supplyService */); + doThrow(RemoteException.class).when(mMockService).retrieveNetworkAttributes(any(), any()); + + final Blob b = new Blob(); + b.data = TEST_BLOB_DATA; + final String l2Key = "fakeKey"; + + // enqueue multiple ipms requests + mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES, + status -> assertTrue("Store not successful : " + status.resultCode, + status.isSuccess())); + mStore.retrieveNetworkAttributes(l2Key, + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(TEST_NETWORK_ATTRIBUTES, attr); + }); + mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b, + status -> assertTrue("Store not successful : " + status.resultCode, + status.isSuccess())); + mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME, + (status, key, name, data) -> { + assertTrue("Retrieve blob status not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(name, TEST_DATA_NAME); + assertTrue(Arrays.equals(b.data, data.data)); + }); + + // verify the rest of the queue is still processed in order even if the remote exception + // occurs when calling one or more requests + InOrder inOrder = inOrder(mMockService); + + inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any()); + inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME), + eq(b), any()); + inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID), + eq(TEST_OTHER_DATA_NAME), any()); + assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue())); + } + + @Test + public void testEnqueuedIpMsRequestsCallbackFunctionWithException() throws Exception { + startIpMemoryStore(true /* supplyService */); + + final Blob b = new Blob(); + b.data = TEST_BLOB_DATA; + final String l2Key = "fakeKey"; + + // enqueue multiple ipms requests + mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES, + status -> assertTrue("Store not successful : " + status.resultCode, + status.isSuccess())); + mStore.retrieveNetworkAttributes(l2Key, + (status, key, attr) -> { + throw new RuntimeException("retrieveNetworkAttributes test"); + }); + mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b, + status -> { + throw new RuntimeException("storeBlob test"); + }); + mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME, + (status, key, name, data) -> { + assertTrue("Retrieve blob status not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(name, TEST_DATA_NAME); + assertTrue(Arrays.equals(b.data, data.data)); + }); + + // verify the rest of the queue is still processed in order even if when one or more + // callback throw the remote exception + InOrder inOrder = inOrder(mMockService); + + inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), + any()); + inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any()); + inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME), + eq(b), any()); + inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID), + eq(TEST_OTHER_DATA_NAME), any()); + assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue())); + } + + @Test + public void testFactoryReset() throws RemoteException { + startIpMemoryStore(true /* supplyService */); + mStore.factoryReset(); + verify(mMockService, times(1)).factoryReset(); + } +} diff --git a/tests/unit/java/android/net/IpSecAlgorithmTest.java b/tests/unit/java/android/net/IpSecAlgorithmTest.java new file mode 100644 index 0000000000..3a8d6004f6 --- /dev/null +++ b/tests/unit/java/android/net/IpSecAlgorithmTest.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2017 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; + +import static android.net.IpSecAlgorithm.ALGO_TO_REQUIRED_FIRST_SDK; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.res.Resources; +import android.os.Build; +import android.os.Parcel; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.CollectionUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; + +/** Unit tests for {@link IpSecAlgorithm}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class IpSecAlgorithmTest { + private static final byte[] KEY_MATERIAL; + + private final Resources mMockResources = mock(Resources.class); + + static { + KEY_MATERIAL = new byte[128]; + new Random().nextBytes(KEY_MATERIAL); + }; + + private static byte[] generateKey(int keyLenInBits) { + return Arrays.copyOf(KEY_MATERIAL, keyLenInBits / 8); + } + + @Test + public void testNoTruncLen() throws Exception { + Entry[] authAndAeadList = + new Entry[] { + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_MD5, 128), + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA1, 160), + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA256, 256), + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA384, 384), + new SimpleEntry<>(IpSecAlgorithm.AUTH_HMAC_SHA512, 512), + new SimpleEntry<>(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, 224), + }; + + // Expect auth and aead algorithms to throw errors if trunclen is omitted. + for (Entry algData : authAndAeadList) { + try { + new IpSecAlgorithm( + algData.getKey(), Arrays.copyOf(KEY_MATERIAL, algData.getValue() / 8)); + fail("Expected exception on unprovided auth trunclen"); + } catch (IllegalArgumentException expected) { + } + } + + // Ensure crypt works with no truncation length supplied. + new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, 256 / 8)); + } + + private void checkAuthKeyAndTruncLenValidation(String algoName, int keyLen, int truncLen) + throws Exception { + new IpSecAlgorithm(algoName, generateKey(keyLen), truncLen); + + try { + new IpSecAlgorithm(algoName, generateKey(keyLen)); + fail("Expected exception on unprovided auth trunclen"); + } catch (IllegalArgumentException pass) { + } + + try { + new IpSecAlgorithm(algoName, generateKey(keyLen + 8), truncLen); + fail("Invalid key length not validated"); + } catch (IllegalArgumentException pass) { + } + + try { + new IpSecAlgorithm(algoName, generateKey(keyLen), truncLen + 1); + fail("Invalid truncation length not validated"); + } catch (IllegalArgumentException pass) { + } + } + + private void checkCryptKeyLenValidation(String algoName, int keyLen) throws Exception { + new IpSecAlgorithm(algoName, generateKey(keyLen)); + + try { + new IpSecAlgorithm(algoName, generateKey(keyLen + 8)); + fail("Invalid key length not validated"); + } catch (IllegalArgumentException pass) { + } + } + + @Test + public void testValidationForAlgosAddedInS() throws Exception { + if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.R) { + return; + } + + for (int len : new int[] {160, 224, 288}) { + checkCryptKeyLenValidation(IpSecAlgorithm.CRYPT_AES_CTR, len); + } + checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_XCBC, 128, 96); + checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_AES_CMAC, 128, 96); + checkAuthKeyAndTruncLenValidation(IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305, 288, 128); + } + + @Test + public void testTruncLenValidation() throws Exception { + for (int truncLen : new int[] {256, 512}) { + new IpSecAlgorithm( + IpSecAlgorithm.AUTH_HMAC_SHA512, + Arrays.copyOf(KEY_MATERIAL, 512 / 8), + truncLen); + } + + for (int truncLen : new int[] {255, 513}) { + try { + new IpSecAlgorithm( + IpSecAlgorithm.AUTH_HMAC_SHA512, + Arrays.copyOf(KEY_MATERIAL, 512 / 8), + truncLen); + fail("Invalid truncation length not validated"); + } catch (IllegalArgumentException pass) { + } + } + } + + @Test + public void testLenValidation() throws Exception { + for (int len : new int[] {128, 192, 256}) { + new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, len / 8)); + } + try { + new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, Arrays.copyOf(KEY_MATERIAL, 384 / 8)); + fail("Invalid key length not validated"); + } catch (IllegalArgumentException pass) { + } + } + + @Test + public void testAlgoNameValidation() throws Exception { + try { + new IpSecAlgorithm("rot13", Arrays.copyOf(KEY_MATERIAL, 128 / 8)); + fail("Invalid algorithm name not validated"); + } catch (IllegalArgumentException pass) { + } + } + + @Test + public void testParcelUnparcel() throws Exception { + IpSecAlgorithm init = + new IpSecAlgorithm( + IpSecAlgorithm.AUTH_HMAC_SHA512, Arrays.copyOf(KEY_MATERIAL, 512 / 8), 256); + + Parcel p = Parcel.obtain(); + p.setDataPosition(0); + init.writeToParcel(p, 0); + + p.setDataPosition(0); + IpSecAlgorithm fin = IpSecAlgorithm.CREATOR.createFromParcel(p); + assertTrue("Parcel/Unparcel failed!", IpSecAlgorithm.equals(init, fin)); + p.recycle(); + } + + private static Set getMandatoryAlgos() { + return CollectionUtils.filter( + ALGO_TO_REQUIRED_FIRST_SDK.keySet(), + i -> Build.VERSION.FIRST_SDK_INT >= ALGO_TO_REQUIRED_FIRST_SDK.get(i)); + } + + private static Set getOptionalAlgos() { + return CollectionUtils.filter( + ALGO_TO_REQUIRED_FIRST_SDK.keySet(), + i -> Build.VERSION.FIRST_SDK_INT < ALGO_TO_REQUIRED_FIRST_SDK.get(i)); + } + + @Test + public void testGetSupportedAlgorithms() throws Exception { + assertTrue(IpSecAlgorithm.getSupportedAlgorithms().containsAll(getMandatoryAlgos())); + assertTrue(ALGO_TO_REQUIRED_FIRST_SDK.keySet().containsAll( + IpSecAlgorithm.getSupportedAlgorithms())); + } + + @Test + public void testLoadAlgos() throws Exception { + final Set optionalAlgoSet = getOptionalAlgos(); + final String[] optionalAlgos = optionalAlgoSet.toArray(new String[0]); + + doReturn(optionalAlgos).when(mMockResources) + .getStringArray(com.android.internal.R.array.config_optionalIpSecAlgorithms); + + final Set enabledAlgos = new HashSet<>(IpSecAlgorithm.loadAlgos(mMockResources)); + final Set expectedAlgos = ALGO_TO_REQUIRED_FIRST_SDK.keySet(); + + assertEquals(expectedAlgos, enabledAlgos); + } +} diff --git a/tests/unit/java/android/net/IpSecConfigTest.java b/tests/unit/java/android/net/IpSecConfigTest.java new file mode 100644 index 0000000000..25e225ef30 --- /dev/null +++ b/tests/unit/java/android/net/IpSecConfigTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 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; + +import static com.android.testutils.ParcelUtils.assertParcelSane; +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link IpSecConfig}. */ +@SmallTest +@RunWith(JUnit4.class) +public class IpSecConfigTest { + + @Test + public void testDefaults() throws Exception { + IpSecConfig c = new IpSecConfig(); + assertEquals(IpSecTransform.MODE_TRANSPORT, c.getMode()); + assertEquals("", c.getSourceAddress()); + assertEquals("", c.getDestinationAddress()); + assertNull(c.getNetwork()); + assertEquals(IpSecTransform.ENCAP_NONE, c.getEncapType()); + assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getEncapSocketResourceId()); + assertEquals(0, c.getEncapRemotePort()); + assertEquals(0, c.getNattKeepaliveInterval()); + assertNull(c.getEncryption()); + assertNull(c.getAuthentication()); + assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId()); + assertEquals(0, c.getXfrmInterfaceId()); + } + + private IpSecConfig getSampleConfig() { + IpSecConfig c = new IpSecConfig(); + c.setMode(IpSecTransform.MODE_TUNNEL); + c.setSourceAddress("0.0.0.0"); + c.setDestinationAddress("1.2.3.4"); + c.setSpiResourceId(1984); + c.setEncryption( + new IpSecAlgorithm( + IpSecAlgorithm.CRYPT_AES_CBC, + new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF})); + c.setAuthentication( + new IpSecAlgorithm( + IpSecAlgorithm.AUTH_HMAC_MD5, + new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0}, + 128)); + c.setAuthenticatedEncryption( + new IpSecAlgorithm( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0, 1, 2, 3, 4 + }, + 128)); + c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP); + c.setEncapSocketResourceId(7); + c.setEncapRemotePort(22); + c.setNattKeepaliveInterval(42); + c.setMarkValue(12); + c.setMarkMask(23); + c.setXfrmInterfaceId(34); + + return c; + } + + @Test + public void testCopyConstructor() { + IpSecConfig original = getSampleConfig(); + IpSecConfig copy = new IpSecConfig(original); + + assertEquals(original, copy); + assertNotSame(original, copy); + } + + @Test + public void testParcelUnparcel() { + assertParcelingIsLossless(new IpSecConfig()); + + IpSecConfig c = getSampleConfig(); + assertParcelSane(c, 15); + } +} diff --git a/tests/unit/java/android/net/IpSecManagerTest.java b/tests/unit/java/android/net/IpSecManagerTest.java new file mode 100644 index 0000000000..730e2d56bd --- /dev/null +++ b/tests/unit/java/android/net/IpSecManagerTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2017 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; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.system.Os; +import android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.IpSecService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +/** Unit tests for {@link IpSecManager}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class IpSecManagerTest { + + private static final int TEST_UDP_ENCAP_PORT = 34567; + private static final int DROID_SPI = 0xD1201D; + private static final int DUMMY_RESOURCE_ID = 0x1234; + + private static final InetAddress GOOGLE_DNS_4; + private static final String VTI_INTF_NAME = "ipsec_test"; + private static final InetAddress VTI_LOCAL_ADDRESS; + private static final LinkAddress VTI_INNER_ADDRESS = new LinkAddress("10.0.1.1/24"); + + static { + try { + // Google Public DNS Addresses; + GOOGLE_DNS_4 = InetAddress.getByName("8.8.8.8"); + VTI_LOCAL_ADDRESS = InetAddress.getByName("8.8.4.4"); + } catch (UnknownHostException e) { + throw new RuntimeException("Could not resolve DNS Addresses", e); + } + } + + private IpSecService mMockIpSecService; + private IpSecManager mIpSecManager; + private MockContext mMockContext = new MockContext() { + @Override + public String getOpPackageName() { + return "fooPackage"; + } + }; + + @Before + public void setUp() throws Exception { + mMockIpSecService = mock(IpSecService.class); + mIpSecManager = new IpSecManager(mMockContext, mMockIpSecService); + } + + /* + * Allocate a specific SPI + * Close SPIs + */ + @Test + public void testAllocSpi() throws Exception { + IpSecSpiResponse spiResp = + new IpSecSpiResponse(IpSecManager.Status.OK, DUMMY_RESOURCE_ID, DROID_SPI); + when(mMockIpSecService.allocateSecurityParameterIndex( + eq(GOOGLE_DNS_4.getHostAddress()), + eq(DROID_SPI), + anyObject())) + .thenReturn(spiResp); + + IpSecManager.SecurityParameterIndex droidSpi = + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, DROID_SPI); + assertEquals(DROID_SPI, droidSpi.getSpi()); + + droidSpi.close(); + + verify(mMockIpSecService).releaseSecurityParameterIndex(DUMMY_RESOURCE_ID); + } + + @Test + public void testAllocRandomSpi() throws Exception { + IpSecSpiResponse spiResp = + new IpSecSpiResponse(IpSecManager.Status.OK, DUMMY_RESOURCE_ID, DROID_SPI); + when(mMockIpSecService.allocateSecurityParameterIndex( + eq(GOOGLE_DNS_4.getHostAddress()), + eq(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX), + anyObject())) + .thenReturn(spiResp); + + IpSecManager.SecurityParameterIndex randomSpi = + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); + + assertEquals(DROID_SPI, randomSpi.getSpi()); + + randomSpi.close(); + + verify(mMockIpSecService).releaseSecurityParameterIndex(DUMMY_RESOURCE_ID); + } + + /* + * Throws resource unavailable exception + */ + @Test + public void testAllocSpiResUnavailableException() throws Exception { + IpSecSpiResponse spiResp = + new IpSecSpiResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE, 0, 0); + when(mMockIpSecService.allocateSecurityParameterIndex( + anyString(), anyInt(), anyObject())) + .thenReturn(spiResp); + + try { + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); + fail("ResourceUnavailableException was not thrown"); + } catch (IpSecManager.ResourceUnavailableException e) { + } + } + + /* + * Throws spi unavailable exception + */ + @Test + public void testAllocSpiSpiUnavailableException() throws Exception { + IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.SPI_UNAVAILABLE, 0, 0); + when(mMockIpSecService.allocateSecurityParameterIndex( + anyString(), anyInt(), anyObject())) + .thenReturn(spiResp); + + try { + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); + fail("ResourceUnavailableException was not thrown"); + } catch (IpSecManager.ResourceUnavailableException e) { + } + } + + /* + * Should throw exception when request spi 0 in IpSecManager + */ + @Test + public void testRequestAllocInvalidSpi() throws Exception { + try { + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, 0); + fail("Able to allocate invalid spi"); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testOpenEncapsulationSocket() throws Exception { + IpSecUdpEncapResponse udpEncapResp = + new IpSecUdpEncapResponse( + IpSecManager.Status.OK, + DUMMY_RESOURCE_ID, + TEST_UDP_ENCAP_PORT, + Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)); + when(mMockIpSecService.openUdpEncapsulationSocket(eq(TEST_UDP_ENCAP_PORT), anyObject())) + .thenReturn(udpEncapResp); + + IpSecManager.UdpEncapsulationSocket encapSocket = + mIpSecManager.openUdpEncapsulationSocket(TEST_UDP_ENCAP_PORT); + assertNotNull(encapSocket.getFileDescriptor()); + assertEquals(TEST_UDP_ENCAP_PORT, encapSocket.getPort()); + + encapSocket.close(); + + verify(mMockIpSecService).closeUdpEncapsulationSocket(DUMMY_RESOURCE_ID); + } + + @Test + public void testApplyTransportModeTransformEnsuresSocketCreation() throws Exception { + Socket socket = new Socket(); + IpSecConfig dummyConfig = new IpSecConfig(); + IpSecTransform dummyTransform = new IpSecTransform(null, dummyConfig); + + // Even if underlying SocketImpl is not initalized, this should force the init, and + // thereby succeed. + mIpSecManager.applyTransportModeTransform( + socket, IpSecManager.DIRECTION_IN, dummyTransform); + + // Check to make sure the FileDescriptor is non-null + assertNotNull(socket.getFileDescriptor$()); + } + + @Test + public void testRemoveTransportModeTransformsForcesSocketCreation() throws Exception { + Socket socket = new Socket(); + + // Even if underlying SocketImpl is not initalized, this should force the init, and + // thereby succeed. + mIpSecManager.removeTransportModeTransforms(socket); + + // Check to make sure the FileDescriptor is non-null + assertNotNull(socket.getFileDescriptor$()); + } + + @Test + public void testOpenEncapsulationSocketOnRandomPort() throws Exception { + IpSecUdpEncapResponse udpEncapResp = + new IpSecUdpEncapResponse( + IpSecManager.Status.OK, + DUMMY_RESOURCE_ID, + TEST_UDP_ENCAP_PORT, + Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)); + + when(mMockIpSecService.openUdpEncapsulationSocket(eq(0), anyObject())) + .thenReturn(udpEncapResp); + + IpSecManager.UdpEncapsulationSocket encapSocket = + mIpSecManager.openUdpEncapsulationSocket(); + + assertNotNull(encapSocket.getFileDescriptor()); + assertEquals(TEST_UDP_ENCAP_PORT, encapSocket.getPort()); + + encapSocket.close(); + + verify(mMockIpSecService).closeUdpEncapsulationSocket(DUMMY_RESOURCE_ID); + } + + @Test + public void testOpenEncapsulationSocketWithInvalidPort() throws Exception { + try { + mIpSecManager.openUdpEncapsulationSocket(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX); + fail("IllegalArgumentException was not thrown"); + } catch (IllegalArgumentException e) { + } + } + + // TODO: add test when applicable transform builder interface is available + + private IpSecManager.IpSecTunnelInterface createAndValidateVti(int resourceId, String intfName) + throws Exception { + IpSecTunnelInterfaceResponse dummyResponse = + new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName); + when(mMockIpSecService.createTunnelInterface( + eq(VTI_LOCAL_ADDRESS.getHostAddress()), eq(GOOGLE_DNS_4.getHostAddress()), + anyObject(), anyObject(), anyString())) + .thenReturn(dummyResponse); + + IpSecManager.IpSecTunnelInterface tunnelIntf = mIpSecManager.createIpSecTunnelInterface( + VTI_LOCAL_ADDRESS, GOOGLE_DNS_4, mock(Network.class)); + + assertNotNull(tunnelIntf); + return tunnelIntf; + } + + @Test + public void testCreateVti() throws Exception { + IpSecManager.IpSecTunnelInterface tunnelIntf = + createAndValidateVti(DUMMY_RESOURCE_ID, VTI_INTF_NAME); + + assertEquals(VTI_INTF_NAME, tunnelIntf.getInterfaceName()); + + tunnelIntf.close(); + verify(mMockIpSecService).deleteTunnelInterface(eq(DUMMY_RESOURCE_ID), anyString()); + } + + @Test + public void testAddRemoveAddressesFromVti() throws Exception { + IpSecManager.IpSecTunnelInterface tunnelIntf = + createAndValidateVti(DUMMY_RESOURCE_ID, VTI_INTF_NAME); + + tunnelIntf.addAddress(VTI_INNER_ADDRESS.getAddress(), + VTI_INNER_ADDRESS.getPrefixLength()); + verify(mMockIpSecService) + .addAddressToTunnelInterface( + eq(DUMMY_RESOURCE_ID), eq(VTI_INNER_ADDRESS), anyString()); + + tunnelIntf.removeAddress(VTI_INNER_ADDRESS.getAddress(), + VTI_INNER_ADDRESS.getPrefixLength()); + verify(mMockIpSecService) + .addAddressToTunnelInterface( + eq(DUMMY_RESOURCE_ID), eq(VTI_INNER_ADDRESS), anyString()); + } +} diff --git a/tests/unit/java/android/net/IpSecTransformTest.java b/tests/unit/java/android/net/IpSecTransformTest.java new file mode 100644 index 0000000000..424f23dbba --- /dev/null +++ b/tests/unit/java/android/net/IpSecTransformTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link IpSecTransform}. */ +@SmallTest +@RunWith(JUnit4.class) +public class IpSecTransformTest { + + @Test + public void testCreateTransformCopiesConfig() { + // Create a config with a few parameters to make sure it's not empty + IpSecConfig config = new IpSecConfig(); + config.setSourceAddress("0.0.0.0"); + config.setDestinationAddress("1.2.3.4"); + config.setSpiResourceId(1984); + + IpSecTransform preModification = new IpSecTransform(null, config); + + config.setSpiResourceId(1985); + IpSecTransform postModification = new IpSecTransform(null, config); + + assertNotEquals(preModification, postModification); + } + + @Test + public void testCreateTransformsWithSameConfigEqual() { + // Create a config with a few parameters to make sure it's not empty + IpSecConfig config = new IpSecConfig(); + config.setSourceAddress("0.0.0.0"); + config.setDestinationAddress("1.2.3.4"); + config.setSpiResourceId(1984); + + IpSecTransform config1 = new IpSecTransform(null, config); + IpSecTransform config2 = new IpSecTransform(null, config); + + assertEquals(config1, config2); + } +} diff --git a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java new file mode 100644 index 0000000000..fc739fbfac --- /dev/null +++ b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java @@ -0,0 +1,205 @@ +/* + * 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 android.net; + +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.util.KeepalivePacketDataUtil; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.net.InetAddress; +import java.nio.ByteBuffer; + +@RunWith(JUnit4.class) +public final class KeepalivePacketDataUtilTest { + private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 1}; + private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 5}; + + @Before + public void setUp() {} + + @Test + public void testFromTcpKeepaliveStableParcelable() throws Exception { + final int srcPort = 1234; + final int dstPort = 4321; + final int seq = 0x11111111; + final int ack = 0x22222222; + final int wnd = 8000; + final int wndScale = 2; + final int tos = 4; + final int ttl = 64; + TcpKeepalivePacketData resultData = null; + final TcpKeepalivePacketDataParcelable testInfo = new TcpKeepalivePacketDataParcelable(); + testInfo.srcAddress = IPV4_KEEPALIVE_SRC_ADDR; + testInfo.srcPort = srcPort; + testInfo.dstAddress = IPV4_KEEPALIVE_DST_ADDR; + testInfo.dstPort = dstPort; + testInfo.seq = seq; + testInfo.ack = ack; + testInfo.rcvWnd = wnd; + testInfo.rcvWndScale = wndScale; + testInfo.tos = tos; + testInfo.ttl = ttl; + try { + resultData = KeepalivePacketDataUtil.fromStableParcelable(testInfo); + } catch (InvalidPacketException e) { + fail("InvalidPacketException: " + e); + } + + assertEquals(InetAddress.getByAddress(testInfo.srcAddress), resultData.getSrcAddress()); + assertEquals(InetAddress.getByAddress(testInfo.dstAddress), resultData.getDstAddress()); + assertEquals(testInfo.srcPort, resultData.getSrcPort()); + assertEquals(testInfo.dstPort, resultData.getDstPort()); + assertEquals(testInfo.seq, resultData.tcpSeq); + assertEquals(testInfo.ack, resultData.tcpAck); + assertEquals(testInfo.rcvWnd, resultData.tcpWindow); + assertEquals(testInfo.rcvWndScale, resultData.tcpWindowScale); + assertEquals(testInfo.tos, resultData.ipTos); + assertEquals(testInfo.ttl, resultData.ipTtl); + + assertParcelingIsLossless(resultData); + + final byte[] packet = resultData.getPacket(); + // IP version and IHL + assertEquals(packet[0], 0x45); + // TOS + assertEquals(packet[1], tos); + // TTL + assertEquals(packet[8], ttl); + // Source IP address. + byte[] ip = new byte[4]; + ByteBuffer buf = ByteBuffer.wrap(packet, 12, 4); + buf.get(ip); + assertArrayEquals(ip, IPV4_KEEPALIVE_SRC_ADDR); + // Destination IP address. + buf = ByteBuffer.wrap(packet, 16, 4); + buf.get(ip); + assertArrayEquals(ip, IPV4_KEEPALIVE_DST_ADDR); + + buf = ByteBuffer.wrap(packet, 20, 12); + // Source port. + assertEquals(buf.getShort(), srcPort); + // Destination port. + assertEquals(buf.getShort(), dstPort); + // Sequence number. + assertEquals(buf.getInt(), seq); + // Ack. + assertEquals(buf.getInt(), ack); + // Window size. + buf = ByteBuffer.wrap(packet, 34, 2); + assertEquals(buf.getShort(), wnd >> wndScale); + } + + //TODO: add ipv6 test when ipv6 supported + + @Test + public void testToTcpKeepaliveStableParcelable() throws Exception { + final int srcPort = 1234; + final int dstPort = 4321; + final int sequence = 0x11111111; + final int ack = 0x22222222; + final int wnd = 48_000; + final int wndScale = 2; + final int tos = 4; + final int ttl = 64; + final TcpKeepalivePacketDataParcelable testInfo = new TcpKeepalivePacketDataParcelable(); + testInfo.srcAddress = IPV4_KEEPALIVE_SRC_ADDR; + testInfo.srcPort = srcPort; + testInfo.dstAddress = IPV4_KEEPALIVE_DST_ADDR; + testInfo.dstPort = dstPort; + testInfo.seq = sequence; + testInfo.ack = ack; + testInfo.rcvWnd = wnd; + testInfo.rcvWndScale = wndScale; + testInfo.tos = tos; + testInfo.ttl = ttl; + TcpKeepalivePacketData testData = null; + TcpKeepalivePacketDataParcelable resultData = null; + testData = KeepalivePacketDataUtil.fromStableParcelable(testInfo); + resultData = KeepalivePacketDataUtil.toStableParcelable(testData); + assertArrayEquals(resultData.srcAddress, IPV4_KEEPALIVE_SRC_ADDR); + assertArrayEquals(resultData.dstAddress, IPV4_KEEPALIVE_DST_ADDR); + assertEquals(resultData.srcPort, srcPort); + assertEquals(resultData.dstPort, dstPort); + assertEquals(resultData.seq, sequence); + assertEquals(resultData.ack, ack); + assertEquals(resultData.rcvWnd, wnd); + assertEquals(resultData.rcvWndScale, wndScale); + assertEquals(resultData.tos, tos); + assertEquals(resultData.ttl, ttl); + + final String expected = "" + + "android.net.TcpKeepalivePacketDataParcelable{srcAddress: [10, 0, 0, 1]," + + " srcPort: 1234, dstAddress: [10, 0, 0, 5], dstPort: 4321, seq: 286331153," + + " ack: 572662306, rcvWnd: 48000, rcvWndScale: 2, tos: 4, ttl: 64}"; + assertEquals(expected, resultData.toString()); + } + + @Test + public void testParseTcpKeepalivePacketData() throws Exception { + final int srcPort = 1234; + final int dstPort = 4321; + final int sequence = 0x11111111; + final int ack = 0x22222222; + final int wnd = 4800; + final int wndScale = 2; + final int tos = 4; + final int ttl = 64; + final TcpKeepalivePacketDataParcelable testParcel = new TcpKeepalivePacketDataParcelable(); + testParcel.srcAddress = IPV4_KEEPALIVE_SRC_ADDR; + testParcel.srcPort = srcPort; + testParcel.dstAddress = IPV4_KEEPALIVE_DST_ADDR; + testParcel.dstPort = dstPort; + testParcel.seq = sequence; + testParcel.ack = ack; + testParcel.rcvWnd = wnd; + testParcel.rcvWndScale = wndScale; + testParcel.tos = tos; + testParcel.ttl = ttl; + + final KeepalivePacketData testData = + KeepalivePacketDataUtil.fromStableParcelable(testParcel); + final TcpKeepalivePacketDataParcelable parsedParcelable = + KeepalivePacketDataUtil.parseTcpKeepalivePacketData(testData); + final TcpKeepalivePacketData roundTripData = + KeepalivePacketDataUtil.fromStableParcelable(parsedParcelable); + + // Generated packet is the same, but rcvWnd / wndScale will differ if scale is non-zero + assertTrue(testData.getPacket().length > 0); + assertArrayEquals(testData.getPacket(), roundTripData.getPacket()); + + testParcel.rcvWndScale = 0; + final KeepalivePacketData noScaleTestData = + KeepalivePacketDataUtil.fromStableParcelable(testParcel); + final TcpKeepalivePacketDataParcelable noScaleParsedParcelable = + KeepalivePacketDataUtil.parseTcpKeepalivePacketData(noScaleTestData); + final TcpKeepalivePacketData noScaleRoundTripData = + KeepalivePacketDataUtil.fromStableParcelable(noScaleParsedParcelable); + assertEquals(noScaleTestData, noScaleRoundTripData); + assertTrue(noScaleTestData.getPacket().length > 0); + assertArrayEquals(noScaleTestData.getPacket(), noScaleRoundTripData.getPacket()); + } +} diff --git a/tests/unit/java/android/net/MacAddressTest.java b/tests/unit/java/android/net/MacAddressTest.java new file mode 100644 index 0000000000..6de31f6b4b --- /dev/null +++ b/tests/unit/java/android/net/MacAddressTest.java @@ -0,0 +1,312 @@ +/* + * Copyright 2017 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; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.net.module.util.MacAddressUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet6Address; +import java.util.Arrays; +import java.util.Random; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class MacAddressTest { + + static class AddrTypeTestCase { + byte[] addr; + int expectedType; + + static AddrTypeTestCase of(int expectedType, int... addr) { + AddrTypeTestCase t = new AddrTypeTestCase(); + t.expectedType = expectedType; + t.addr = toByteArray(addr); + return t; + } + } + + @Test + public void testMacAddrTypes() { + AddrTypeTestCase[] testcases = { + AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN), + AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 0), + AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 1, 2, 3, 4, 5), + AddrTypeTestCase.of(MacAddress.TYPE_UNKNOWN, 1, 2, 3, 4, 5, 6, 7), + AddrTypeTestCase.of(MacAddress.TYPE_UNICAST, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0), + AddrTypeTestCase.of(MacAddress.TYPE_BROADCAST, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff), + AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 1, 2, 3, 4, 5, 6), + AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 11, 22, 33, 44, 55, 66), + AddrTypeTestCase.of(MacAddress.TYPE_MULTICAST, 33, 33, 0xaa, 0xbb, 0xcc, 0xdd) + }; + + for (AddrTypeTestCase t : testcases) { + int got = MacAddress.macAddressType(t.addr); + String msg = String.format("expected type of %s to be %s, but got %s", + Arrays.toString(t.addr), t.expectedType, got); + assertEquals(msg, t.expectedType, got); + + if (got != MacAddress.TYPE_UNKNOWN) { + assertEquals(got, MacAddress.fromBytes(t.addr).getAddressType()); + } + } + } + + @Test + public void testToOuiString() { + String[][] macs = { + {"07:00:d3:56:8a:c4", "07:00:d3"}, + {"33:33:aa:bb:cc:dd", "33:33:aa"}, + {"06:00:00:00:00:00", "06:00:00"}, + {"07:00:d3:56:8a:c4", "07:00:d3"} + }; + + for (String[] pair : macs) { + String mac = pair[0]; + String expected = pair[1]; + assertEquals(expected, MacAddress.fromString(mac).toOuiString()); + } + } + + @Test + public void testHexPaddingWhenPrinting() { + String[] macs = { + "07:00:d3:56:8a:c4", + "33:33:aa:bb:cc:dd", + "06:00:00:00:00:00", + "07:00:d3:56:8a:c4" + }; + + for (String mac : macs) { + assertEquals(mac, MacAddress.fromString(mac).toString()); + assertEquals(mac, + MacAddress.stringAddrFromByteAddr(MacAddress.byteAddrFromStringAddr(mac))); + } + } + + @Test + public void testIsMulticastAddress() { + MacAddress[] multicastAddresses = { + MacAddress.BROADCAST_ADDRESS, + MacAddress.fromString("07:00:d3:56:8a:c4"), + MacAddress.fromString("33:33:aa:bb:cc:dd"), + }; + MacAddress[] unicastAddresses = { + MacAddress.ALL_ZEROS_ADDRESS, + MacAddress.fromString("00:01:44:55:66:77"), + MacAddress.fromString("08:00:22:33:44:55"), + MacAddress.fromString("06:00:00:00:00:00"), + }; + + for (MacAddress mac : multicastAddresses) { + String msg = mac.toString() + " expected to be a multicast address"; + assertTrue(msg, MacAddressUtils.isMulticastAddress(mac)); + } + for (MacAddress mac : unicastAddresses) { + String msg = mac.toString() + " expected not to be a multicast address"; + assertFalse(msg, MacAddressUtils.isMulticastAddress(mac)); + } + } + + @Test + public void testIsLocallyAssignedAddress() { + MacAddress[] localAddresses = { + MacAddress.fromString("06:00:00:00:00:00"), + MacAddress.fromString("07:00:d3:56:8a:c4"), + MacAddress.fromString("33:33:aa:bb:cc:dd"), + }; + MacAddress[] universalAddresses = { + MacAddress.fromString("00:01:44:55:66:77"), + MacAddress.fromString("08:00:22:33:44:55"), + }; + + for (MacAddress mac : localAddresses) { + String msg = mac.toString() + " expected to be a locally assigned address"; + assertTrue(msg, mac.isLocallyAssigned()); + } + for (MacAddress mac : universalAddresses) { + String msg = mac.toString() + " expected not to be globally unique address"; + assertFalse(msg, mac.isLocallyAssigned()); + } + } + + @Test + public void testMacAddressConversions() { + final int iterations = 10000; + for (int i = 0; i < iterations; i++) { + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(); + + String stringRepr = mac.toString(); + byte[] bytesRepr = mac.toByteArray(); + + assertEquals(mac, MacAddress.fromString(stringRepr)); + assertEquals(mac, MacAddress.fromBytes(bytesRepr)); + + assertEquals(mac, MacAddress.fromString(MacAddress.stringAddrFromByteAddr(bytesRepr))); + assertEquals(mac, MacAddress.fromBytes(MacAddress.byteAddrFromStringAddr(stringRepr))); + } + } + + @Test + public void testMacAddressRandomGeneration() { + final int iterations = 1000; + final String expectedAndroidOui = "da:a1:19"; + for (int i = 0; i < iterations; i++) { + MacAddress mac = MacAddress.createRandomUnicastAddressWithGoogleBase(); + String stringRepr = mac.toString(); + + assertTrue(stringRepr + " expected to be a locally assigned address", + mac.isLocallyAssigned()); + assertTrue(stringRepr + " expected to begin with " + expectedAndroidOui, + stringRepr.startsWith(expectedAndroidOui)); + } + + final Random r = new Random(); + final String anotherOui = "24:5f:78"; + final String expectedLocalOui = "26:5f:78"; + final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0"); + for (int i = 0; i < iterations; i++) { + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r); + String stringRepr = mac.toString(); + + assertTrue(stringRepr + " expected to be a locally assigned address", + mac.isLocallyAssigned()); + assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType()); + assertTrue(stringRepr + " expected to begin with " + expectedLocalOui, + stringRepr.startsWith(expectedLocalOui)); + } + + for (int i = 0; i < iterations; i++) { + MacAddress mac = MacAddressUtils.createRandomUnicastAddress(); + String stringRepr = mac.toString(); + + assertTrue(stringRepr + " expected to be a locally assigned address", + mac.isLocallyAssigned()); + assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType()); + } + } + + @Test + public void testConstructorInputValidation() { + String[] invalidStringAddresses = { + "", + "abcd", + "1:2:3:4:5", + "1:2:3:4:5:6:7", + "10000:2:3:4:5:6", + }; + + for (String s : invalidStringAddresses) { + try { + MacAddress mac = MacAddress.fromString(s); + fail("MacAddress.fromString(" + s + ") should have failed, but returned " + mac); + } catch (IllegalArgumentException excepted) { + } + } + + try { + MacAddress mac = MacAddress.fromString(null); + fail("MacAddress.fromString(null) should have failed, but returned " + mac); + } catch (NullPointerException excepted) { + } + + byte[][] invalidBytesAddresses = { + {}, + {1,2,3,4,5}, + {1,2,3,4,5,6,7}, + }; + + for (byte[] b : invalidBytesAddresses) { + try { + MacAddress mac = MacAddress.fromBytes(b); + fail("MacAddress.fromBytes(" + Arrays.toString(b) + + ") should have failed, but returned " + mac); + } catch (IllegalArgumentException excepted) { + } + } + + try { + MacAddress mac = MacAddress.fromBytes(null); + fail("MacAddress.fromBytes(null) should have failed, but returned " + mac); + } catch (NullPointerException excepted) { + } + } + + @Test + public void testMatches() { + // match 4 bytes prefix + assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches( + MacAddress.fromString("aa:bb:cc:dd:00:00"), + MacAddress.fromString("ff:ff:ff:ff:00:00"))); + + // match bytes 0,1,2 and 5 + assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches( + MacAddress.fromString("aa:bb:cc:00:00:11"), + MacAddress.fromString("ff:ff:ff:00:00:ff"))); + + // match 34 bit prefix + assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches( + MacAddress.fromString("aa:bb:cc:dd:c0:00"), + MacAddress.fromString("ff:ff:ff:ff:c0:00"))); + + // fail to match 36 bit prefix + assertFalse(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches( + MacAddress.fromString("aa:bb:cc:dd:40:00"), + MacAddress.fromString("ff:ff:ff:ff:f0:00"))); + + // match all 6 bytes + assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches( + MacAddress.fromString("aa:bb:cc:dd:ee:11"), + MacAddress.fromString("ff:ff:ff:ff:ff:ff"))); + + // match none of 6 bytes + assertTrue(MacAddress.fromString("aa:bb:cc:dd:ee:11").matches( + MacAddress.fromString("00:00:00:00:00:00"), + MacAddress.fromString("00:00:00:00:00:00"))); + } + + /** + * Tests that link-local address generation from MAC is valid. + */ + @Test + public void testLinkLocalFromMacGeneration() { + MacAddress mac = MacAddress.fromString("52:74:f2:b1:a8:7f"); + byte[] inet6ll = {(byte) 0xfe, (byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x74, + (byte) 0xf2, (byte) 0xff, (byte) 0xfe, (byte) 0xb1, (byte) 0xa8, 0x7f}; + Inet6Address llv6 = mac.getLinkLocalIpv6FromEui48Mac(); + assertTrue(llv6.isLinkLocalAddress()); + assertArrayEquals(inet6ll, llv6.getAddress()); + } + + static byte[] toByteArray(int... in) { + byte[] out = new byte[in.length]; + for (int i = 0; i < in.length; i++) { + out[i] = (byte) in[i]; + } + return out; + } +} diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt new file mode 100644 index 0000000000..eb2b85c145 --- /dev/null +++ b/tests/unit/java/android/net/NetworkIdentityTest.kt @@ -0,0 +1,54 @@ +/* + * 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 android.net + +import android.net.NetworkIdentity.OEM_NONE +import android.net.NetworkIdentity.OEM_PAID +import android.net.NetworkIdentity.OEM_PRIVATE +import android.net.NetworkIdentity.getOemBitfield +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import kotlin.test.assertEquals + +@RunWith(JUnit4::class) +class NetworkIdentityTest { + @Test + fun testGetOemBitfield() { + val oemNone = NetworkCapabilities().apply { + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, false) + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, false) + } + val oemPaid = NetworkCapabilities().apply { + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, true) + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, false) + } + val oemPrivate = NetworkCapabilities().apply { + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, false) + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, true) + } + val oemAll = NetworkCapabilities().apply { + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, true) + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, true) + } + + assertEquals(getOemBitfield(oemNone), OEM_NONE) + assertEquals(getOemBitfield(oemPaid), OEM_PAID) + assertEquals(getOemBitfield(oemPrivate), OEM_PRIVATE) + assertEquals(getOemBitfield(oemAll), OEM_PAID or OEM_PRIVATE) + } +} diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java new file mode 100644 index 0000000000..13558cd51c --- /dev/null +++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java @@ -0,0 +1,599 @@ +/* + * Copyright (C) 2011 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; + +import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLong; +import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLong; +import static android.net.NetworkStatsHistory.Entry.UNKNOWN; +import static android.net.NetworkStatsHistory.FIELD_ALL; +import static android.net.NetworkStatsHistory.FIELD_OPERATIONS; +import static android.net.NetworkStatsHistory.FIELD_RX_BYTES; +import static android.net.NetworkStatsHistory.FIELD_RX_PACKETS; +import static android.net.NetworkStatsHistory.FIELD_TX_BYTES; +import static android.net.TrafficStats.GB_IN_BYTES; +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import static android.text.format.DateUtils.SECOND_IN_MILLIS; +import static android.text.format.DateUtils.WEEK_IN_MILLIS; +import static android.text.format.DateUtils.YEAR_IN_MILLIS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.tests.net.R; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.util.Random; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsHistoryTest { + private static final String TAG = "NetworkStatsHistoryTest"; + + private static final long TEST_START = 1194220800000L; + + private NetworkStatsHistory stats; + + @After + public void tearDown() throws Exception { + if (stats != null) { + assertConsistent(stats); + } + } + + @Test + public void testReadOriginalVersion() throws Exception { + final Context context = InstrumentationRegistry.getContext(); + final DataInputStream in = + new DataInputStream(context.getResources().openRawResource(R.raw.history_v1)); + + NetworkStatsHistory.Entry entry = null; + try { + final NetworkStatsHistory history = new NetworkStatsHistory(in); + assertEquals(15 * SECOND_IN_MILLIS, history.getBucketDuration()); + + entry = history.getValues(0, entry); + assertEquals(29143L, entry.rxBytes); + assertEquals(6223L, entry.txBytes); + + entry = history.getValues(history.size() - 1, entry); + assertEquals(1476L, entry.rxBytes); + assertEquals(838L, entry.txBytes); + + entry = history.getValues(Long.MIN_VALUE, Long.MAX_VALUE, entry); + assertEquals(332401L, entry.rxBytes); + assertEquals(64314L, entry.txBytes); + + } finally { + in.close(); + } + } + + @Test + public void testRecordSingleBucket() throws Exception { + final long BUCKET_SIZE = HOUR_IN_MILLIS; + stats = new NetworkStatsHistory(BUCKET_SIZE); + + // record data into narrow window to get single bucket + stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + + assertEquals(1, stats.size()); + assertValues(stats, 0, SECOND_IN_MILLIS, 1024L, 10L, 2048L, 20L, 2L); + } + + @Test + public void testRecordEqualBuckets() throws Exception { + final long bucketDuration = HOUR_IN_MILLIS; + stats = new NetworkStatsHistory(bucketDuration); + + // split equally across two buckets + final long recordStart = TEST_START + (bucketDuration / 2); + stats.recordData(recordStart, recordStart + bucketDuration, + new NetworkStats.Entry(1024L, 10L, 128L, 2L, 2L)); + + assertEquals(2, stats.size()); + assertValues(stats, 0, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L); + assertValues(stats, 1, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L); + } + + @Test + public void testRecordTouchingBuckets() throws Exception { + final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS; + stats = new NetworkStatsHistory(BUCKET_SIZE); + + // split almost completely into middle bucket, but with a few minutes + // overlap into neighboring buckets. total record is 20 minutes. + final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS; + final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4); + stats.recordData(recordStart, recordEnd, + new NetworkStats.Entry(1000L, 2000L, 5000L, 10000L, 100L)); + + assertEquals(3, stats.size()); + // first bucket should have (1/20 of value) + assertValues(stats, 0, MINUTE_IN_MILLIS, 50L, 100L, 250L, 500L, 5L); + // second bucket should have (15/20 of value) + assertValues(stats, 1, 15 * MINUTE_IN_MILLIS, 750L, 1500L, 3750L, 7500L, 75L); + // final bucket should have (4/20 of value) + assertValues(stats, 2, 4 * MINUTE_IN_MILLIS, 200L, 400L, 1000L, 2000L, 20L); + } + + @Test + public void testRecordGapBuckets() throws Exception { + final long BUCKET_SIZE = HOUR_IN_MILLIS; + stats = new NetworkStatsHistory(BUCKET_SIZE); + + // record some data today and next week with large gap + final long firstStart = TEST_START; + final long lastStart = TEST_START + WEEK_IN_MILLIS; + stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS, + new NetworkStats.Entry(128L, 2L, 256L, 4L, 1L)); + stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS, + new NetworkStats.Entry(64L, 1L, 512L, 8L, 2L)); + + // we should have two buckets, far apart from each other + assertEquals(2, stats.size()); + assertValues(stats, 0, SECOND_IN_MILLIS, 128L, 2L, 256L, 4L, 1L); + assertValues(stats, 1, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L); + + // now record something in middle, spread across two buckets + final long middleStart = TEST_START + DAY_IN_MILLIS; + final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2); + stats.recordData(middleStart, middleEnd, + new NetworkStats.Entry(2048L, 4L, 2048L, 4L, 2L)); + + // now should have four buckets, with new record in middle two buckets + assertEquals(4, stats.size()); + assertValues(stats, 0, SECOND_IN_MILLIS, 128L, 2L, 256L, 4L, 1L); + assertValues(stats, 1, HOUR_IN_MILLIS, 1024L, 2L, 1024L, 2L, 1L); + assertValues(stats, 2, HOUR_IN_MILLIS, 1024L, 2L, 1024L, 2L, 1L); + assertValues(stats, 3, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L); + } + + @Test + public void testRecordOverlapBuckets() throws Exception { + final long BUCKET_SIZE = HOUR_IN_MILLIS; + stats = new NetworkStatsHistory(BUCKET_SIZE); + + // record some data in one bucket, and another overlapping buckets + stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, + new NetworkStats.Entry(256L, 2L, 256L, 2L, 1L)); + final long midStart = TEST_START + (HOUR_IN_MILLIS / 2); + stats.recordData(midStart, midStart + HOUR_IN_MILLIS, + new NetworkStats.Entry(1024L, 10L, 1024L, 10L, 10L)); + + // should have two buckets, with some data mixed together + assertEquals(2, stats.size()); + assertValues(stats, 0, SECOND_IN_MILLIS + (HOUR_IN_MILLIS / 2), 768L, 7L, 768L, 7L, 6L); + assertValues(stats, 1, (HOUR_IN_MILLIS / 2), 512L, 5L, 512L, 5L, 5L); + } + + @Test + public void testRecordEntireGapIdentical() throws Exception { + // first, create two separate histories far apart + final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS); + stats1.recordData(TEST_START, TEST_START + 2 * HOUR_IN_MILLIS, 2000L, 1000L); + + final long TEST_START_2 = TEST_START + DAY_IN_MILLIS; + final NetworkStatsHistory stats2 = new NetworkStatsHistory(HOUR_IN_MILLIS); + stats2.recordData(TEST_START_2, TEST_START_2 + 2 * HOUR_IN_MILLIS, 1000L, 500L); + + // combine together with identical bucket size + stats = new NetworkStatsHistory(HOUR_IN_MILLIS); + stats.recordEntireHistory(stats1); + stats.recordEntireHistory(stats2); + + // first verify that totals match up + assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 3000L, 1500L); + + // now inspect internal buckets + assertValues(stats, 0, 1000L, 500L); + assertValues(stats, 1, 1000L, 500L); + assertValues(stats, 2, 500L, 250L); + assertValues(stats, 3, 500L, 250L); + } + + @Test + public void testRecordEntireOverlapVaryingBuckets() throws Exception { + // create history just over hour bucket boundary + final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS); + stats1.recordData(TEST_START, TEST_START + MINUTE_IN_MILLIS * 60, 600L, 600L); + + final long TEST_START_2 = TEST_START + MINUTE_IN_MILLIS; + final NetworkStatsHistory stats2 = new NetworkStatsHistory(MINUTE_IN_MILLIS); + stats2.recordData(TEST_START_2, TEST_START_2 + MINUTE_IN_MILLIS * 5, 50L, 50L); + + // combine together with minute bucket size + stats = new NetworkStatsHistory(MINUTE_IN_MILLIS); + stats.recordEntireHistory(stats1); + stats.recordEntireHistory(stats2); + + // first verify that totals match up + assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L); + + // now inspect internal buckets + assertValues(stats, 0, 10L, 10L); + assertValues(stats, 1, 20L, 20L); + assertValues(stats, 2, 20L, 20L); + assertValues(stats, 3, 20L, 20L); + assertValues(stats, 4, 20L, 20L); + assertValues(stats, 5, 20L, 20L); + assertValues(stats, 6, 10L, 10L); + + // now combine using 15min buckets + stats = new NetworkStatsHistory(HOUR_IN_MILLIS / 4); + stats.recordEntireHistory(stats1); + stats.recordEntireHistory(stats2); + + // first verify that totals match up + assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 650L, 650L); + + // and inspect buckets + assertValues(stats, 0, 200L, 200L); + assertValues(stats, 1, 150L, 150L); + assertValues(stats, 2, 150L, 150L); + assertValues(stats, 3, 150L, 150L); + } + + @Test + public void testRemove() throws Exception { + stats = new NetworkStatsHistory(HOUR_IN_MILLIS); + + // record some data across 24 buckets + stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L); + assertEquals(24, stats.size()); + + // try removing invalid data; should be no change + stats.removeBucketsBefore(0 - DAY_IN_MILLIS); + assertEquals(24, stats.size()); + + // try removing far before buckets; should be no change + stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS); + assertEquals(24, stats.size()); + + // try removing just moments into first bucket; should be no change + // since that bucket contains data beyond the cutoff + stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS); + assertEquals(24, stats.size()); + + // try removing single bucket + stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS); + assertEquals(23, stats.size()); + + // try removing multiple buckets + stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS)); + assertEquals(20, stats.size()); + + // try removing all buckets + stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS); + assertEquals(0, stats.size()); + } + + @Test + public void testTotalData() throws Exception { + final long BUCKET_SIZE = HOUR_IN_MILLIS; + stats = new NetworkStatsHistory(BUCKET_SIZE); + + // record uniform data across day + stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 2400L, 4800L); + + // verify that total outside range is 0 + assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START - DAY_IN_MILLIS, 0L, 0L); + + // verify total in first hour + assertValues(stats, TEST_START, TEST_START + HOUR_IN_MILLIS, 100L, 200L); + + // verify total across 1.5 hours + assertValues(stats, TEST_START, TEST_START + (long) (1.5 * HOUR_IN_MILLIS), 150L, 300L); + + // verify total beyond end + assertValues(stats, TEST_START + (23 * HOUR_IN_MILLIS), TEST_START + WEEK_IN_MILLIS, 100L, 200L); + + // verify everything total + assertValues(stats, TEST_START - WEEK_IN_MILLIS, TEST_START + WEEK_IN_MILLIS, 2400L, 4800L); + + } + + @Test + public void testFuzzing() throws Exception { + try { + // fuzzing with random events, looking for crashes + final NetworkStats.Entry entry = new NetworkStats.Entry(); + final Random r = new Random(); + for (int i = 0; i < 500; i++) { + stats = new NetworkStatsHistory(r.nextLong()); + for (int j = 0; j < 10000; j++) { + if (r.nextBoolean()) { + // add range + final long start = r.nextLong(); + final long end = start + r.nextInt(); + entry.rxBytes = nextPositiveLong(r); + entry.rxPackets = nextPositiveLong(r); + entry.txBytes = nextPositiveLong(r); + entry.txPackets = nextPositiveLong(r); + entry.operations = nextPositiveLong(r); + stats.recordData(start, end, entry); + } else { + // trim something + stats.removeBucketsBefore(r.nextLong()); + } + } + assertConsistent(stats); + } + } catch (Throwable e) { + Log.e(TAG, String.valueOf(stats)); + throw new RuntimeException(e); + } + } + + private static long nextPositiveLong(Random r) { + final long value = r.nextLong(); + return value < 0 ? -value : value; + } + + @Test + public void testIgnoreFields() throws Exception { + final NetworkStatsHistory history = new NetworkStatsHistory( + MINUTE_IN_MILLIS, 0, FIELD_RX_BYTES | FIELD_TX_BYTES); + + history.recordData(0, MINUTE_IN_MILLIS, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L)); + history.recordData(0, 2 * MINUTE_IN_MILLIS, + new NetworkStats.Entry(2L, 2L, 2L, 2L, 2L)); + + assertFullValues(history, UNKNOWN, 1026L, UNKNOWN, 2050L, UNKNOWN, UNKNOWN); + } + + @Test + public void testIgnoreFieldsRecordIn() throws Exception { + final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL); + final NetworkStatsHistory partial = new NetworkStatsHistory( + MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS); + + full.recordData(0, MINUTE_IN_MILLIS, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L)); + partial.recordEntireHistory(full); + + assertFullValues(partial, UNKNOWN, UNKNOWN, 10L, UNKNOWN, UNKNOWN, 4L); + } + + @Test + public void testIgnoreFieldsRecordOut() throws Exception { + final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL); + final NetworkStatsHistory partial = new NetworkStatsHistory( + MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS); + + partial.recordData(0, MINUTE_IN_MILLIS, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L)); + full.recordEntireHistory(partial); + + assertFullValues(full, MINUTE_IN_MILLIS, 0L, 10L, 0L, 0L, 4L); + } + + @Test + public void testSerialize() throws Exception { + final NetworkStatsHistory before = new NetworkStatsHistory(MINUTE_IN_MILLIS, 40, FIELD_ALL); + before.recordData(0, 4 * MINUTE_IN_MILLIS, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L)); + before.recordData(DAY_IN_MILLIS, DAY_IN_MILLIS + MINUTE_IN_MILLIS, + new NetworkStats.Entry(10L, 20L, 30L, 40L, 50L)); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + before.writeToStream(new DataOutputStream(out)); + out.close(); + + final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + final NetworkStatsHistory after = new NetworkStatsHistory(new DataInputStream(in)); + + // must have identical totals before and after + assertFullValues(before, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L); + assertFullValues(after, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L); + } + + @Test + public void testVarLong() throws Exception { + assertEquals(0L, performVarLong(0L)); + assertEquals(-1L, performVarLong(-1L)); + assertEquals(1024L, performVarLong(1024L)); + assertEquals(-1024L, performVarLong(-1024L)); + assertEquals(40 * MB_IN_BYTES, performVarLong(40 * MB_IN_BYTES)); + assertEquals(512 * GB_IN_BYTES, performVarLong(512 * GB_IN_BYTES)); + assertEquals(Long.MIN_VALUE, performVarLong(Long.MIN_VALUE)); + assertEquals(Long.MAX_VALUE, performVarLong(Long.MAX_VALUE)); + assertEquals(Long.MIN_VALUE + 40, performVarLong(Long.MIN_VALUE + 40)); + assertEquals(Long.MAX_VALUE - 40, performVarLong(Long.MAX_VALUE - 40)); + } + + @Test + public void testIndexBeforeAfter() throws Exception { + final long BUCKET_SIZE = HOUR_IN_MILLIS; + stats = new NetworkStatsHistory(BUCKET_SIZE); + + final long FIRST_START = TEST_START; + final long FIRST_END = FIRST_START + (2 * HOUR_IN_MILLIS); + final long SECOND_START = TEST_START + WEEK_IN_MILLIS; + final long SECOND_END = SECOND_START + HOUR_IN_MILLIS; + final long THIRD_START = TEST_START + (2 * WEEK_IN_MILLIS); + final long THIRD_END = THIRD_START + (2 * HOUR_IN_MILLIS); + + stats.recordData(FIRST_START, FIRST_END, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + stats.recordData(SECOND_START, SECOND_END, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + stats.recordData(THIRD_START, THIRD_END, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + + // should have buckets: 2+1+2 + assertEquals(5, stats.size()); + + assertIndexBeforeAfter(stats, 0, 0, Long.MIN_VALUE); + assertIndexBeforeAfter(stats, 0, 1, FIRST_START); + assertIndexBeforeAfter(stats, 0, 1, FIRST_START + MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 0, 2, FIRST_START + HOUR_IN_MILLIS); + assertIndexBeforeAfter(stats, 1, 2, FIRST_START + HOUR_IN_MILLIS + MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 1, 2, FIRST_END - MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 1, 2, FIRST_END); + assertIndexBeforeAfter(stats, 1, 2, FIRST_END + MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 1, 2, SECOND_START - MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 1, 3, SECOND_START); + assertIndexBeforeAfter(stats, 2, 3, SECOND_END); + assertIndexBeforeAfter(stats, 2, 3, SECOND_END + MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 2, 3, THIRD_START - MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 2, 4, THIRD_START); + assertIndexBeforeAfter(stats, 3, 4, THIRD_START + MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 3, 4, THIRD_START + HOUR_IN_MILLIS); + assertIndexBeforeAfter(stats, 4, 4, THIRD_END); + assertIndexBeforeAfter(stats, 4, 4, THIRD_END + MINUTE_IN_MILLIS); + assertIndexBeforeAfter(stats, 4, 4, Long.MAX_VALUE); + } + + @Test + public void testIntersects() throws Exception { + final long BUCKET_SIZE = HOUR_IN_MILLIS; + stats = new NetworkStatsHistory(BUCKET_SIZE); + + final long FIRST_START = TEST_START; + final long FIRST_END = FIRST_START + (2 * HOUR_IN_MILLIS); + final long SECOND_START = TEST_START + WEEK_IN_MILLIS; + final long SECOND_END = SECOND_START + HOUR_IN_MILLIS; + final long THIRD_START = TEST_START + (2 * WEEK_IN_MILLIS); + final long THIRD_END = THIRD_START + (2 * HOUR_IN_MILLIS); + + stats.recordData(FIRST_START, FIRST_END, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + stats.recordData(SECOND_START, SECOND_END, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + stats.recordData(THIRD_START, THIRD_END, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + + assertFalse(stats.intersects(10, 20)); + assertFalse(stats.intersects(TEST_START + YEAR_IN_MILLIS, TEST_START + YEAR_IN_MILLIS + 1)); + assertFalse(stats.intersects(Long.MAX_VALUE, Long.MIN_VALUE)); + + assertTrue(stats.intersects(Long.MIN_VALUE, Long.MAX_VALUE)); + assertTrue(stats.intersects(10, TEST_START + YEAR_IN_MILLIS)); + assertTrue(stats.intersects(TEST_START, TEST_START)); + assertTrue(stats.intersects(TEST_START + DAY_IN_MILLIS, TEST_START + DAY_IN_MILLIS + 1)); + assertTrue(stats.intersects(TEST_START + DAY_IN_MILLIS, Long.MAX_VALUE)); + assertTrue(stats.intersects(TEST_START + 1, Long.MAX_VALUE)); + + assertFalse(stats.intersects(Long.MIN_VALUE, TEST_START - 1)); + assertTrue(stats.intersects(Long.MIN_VALUE, TEST_START)); + assertTrue(stats.intersects(Long.MIN_VALUE, TEST_START + 1)); + } + + @Test + public void testSetValues() throws Exception { + stats = new NetworkStatsHistory(HOUR_IN_MILLIS); + stats.recordData(TEST_START, TEST_START + 1, + new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L)); + + assertEquals(1024L + 2048L, stats.getTotalBytes()); + + final NetworkStatsHistory.Entry entry = stats.getValues(0, null); + entry.rxBytes /= 2; + entry.txBytes *= 2; + stats.setValues(0, entry); + + assertEquals(512L + 4096L, stats.getTotalBytes()); + } + + private static void assertIndexBeforeAfter( + NetworkStatsHistory stats, int before, int after, long time) { + assertEquals("unexpected before", before, stats.getIndexBefore(time)); + assertEquals("unexpected after", after, stats.getIndexAfter(time)); + } + + private static long performVarLong(long before) throws Exception { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + writeVarLong(new DataOutputStream(out), before); + + final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + return readVarLong(new DataInputStream(in)); + } + + private static void assertConsistent(NetworkStatsHistory stats) { + // verify timestamps are monotonic + long lastStart = Long.MIN_VALUE; + NetworkStatsHistory.Entry entry = null; + for (int i = 0; i < stats.size(); i++) { + entry = stats.getValues(i, entry); + assertTrue(lastStart < entry.bucketStart); + lastStart = entry.bucketStart; + } + } + + private static void assertValues( + NetworkStatsHistory stats, int index, long rxBytes, long txBytes) { + final NetworkStatsHistory.Entry entry = stats.getValues(index, null); + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + } + + private static void assertValues( + NetworkStatsHistory stats, long start, long end, long rxBytes, long txBytes) { + final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null); + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + } + + private static void assertValues(NetworkStatsHistory stats, int index, long activeTime, + long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { + final NetworkStatsHistory.Entry entry = stats.getValues(index, null); + assertEquals("unexpected activeTime", activeTime, entry.activeTime); + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + assertEquals("unexpected txPackets", txPackets, entry.txPackets); + assertEquals("unexpected operations", operations, entry.operations); + } + + private static void assertFullValues(NetworkStatsHistory stats, long activeTime, long rxBytes, + long rxPackets, long txBytes, long txPackets, long operations) { + assertValues(stats, Long.MIN_VALUE, Long.MAX_VALUE, activeTime, rxBytes, rxPackets, txBytes, + txPackets, operations); + } + + private static void assertValues(NetworkStatsHistory stats, long start, long end, + long activeTime, long rxBytes, long rxPackets, long txBytes, long txPackets, + long operations) { + final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null); + assertEquals("unexpected activeTime", activeTime, entry.activeTime); + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + assertEquals("unexpected txPackets", txPackets, entry.txPackets); + assertEquals("unexpected operations", operations, entry.operations); + } +} diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java new file mode 100644 index 0000000000..23d5a7e5d5 --- /dev/null +++ b/tests/unit/java/android/net/NetworkStatsTest.java @@ -0,0 +1,1024 @@ +/* + * Copyright (C) 2011 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; + +import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.INTERFACES_ALL; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkStats.ROAMING_ALL; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.ROAMING_YES; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DBG_VPN_IN; +import static android.net.NetworkStats.SET_DBG_VPN_OUT; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.SET_FOREGROUND; +import static android.net.NetworkStats.TAG_ALL; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.os.Process; +import android.util.ArrayMap; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.google.android.collect.Sets; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.HashSet; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsTest { + + private static final String TEST_IFACE = "test0"; + private static final String TEST_IFACE2 = "test2"; + private static final int TEST_UID = 1001; + private static final long TEST_START = 1194220800000L; + + @Test + public void testFindIndex() throws Exception { + final NetworkStats stats = new NetworkStats(TEST_START, 5) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11) + .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12) + .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12); + + assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, + ROAMING_YES, DEFAULT_NETWORK_YES)); + assertEquals(3, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO)); + assertEquals(2, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, + ROAMING_NO, DEFAULT_NETWORK_YES)); + assertEquals(1, stats.findIndex(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO)); + assertEquals(0, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_YES)); + assertEquals(-1, stats.findIndex(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO)); + assertEquals(-1, stats.findIndex(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO)); + } + + @Test + public void testFindIndexHinted() { + final NetworkStats stats = new NetworkStats(TEST_START, 3) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) + .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1024L, 8L, 0L, 0L, 10) + .insertEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11) + .insertEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) + .insertEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12) + .insertEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12); + + // verify that we correctly find across regardless of hinting + for (int hint = 0; hint < stats.size(); hint++) { + assertEquals(0, stats.findIndexHinted(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint)); + assertEquals(1, stats.findIndexHinted(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, hint)); + assertEquals(2, stats.findIndexHinted(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint)); + assertEquals(3, stats.findIndexHinted(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, hint)); + assertEquals(4, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint)); + assertEquals(5, stats.findIndexHinted(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, hint)); + assertEquals(6, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint)); + assertEquals(7, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_YES, DEFAULT_NETWORK_NO, hint)); + assertEquals(-1, stats.findIndexHinted(TEST_IFACE, 6, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, hint)); + assertEquals(-1, stats.findIndexHinted(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_YES, DEFAULT_NETWORK_YES, hint)); + } + } + + @Test + public void testAddEntryGrow() throws Exception { + final NetworkStats stats = new NetworkStats(TEST_START, 4); + + assertEquals(0, stats.size()); + assertEquals(4, stats.internalSize()); + + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3); + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4); + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5); + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5); + + assertEquals(4, stats.size()); + assertEquals(4, stats.internalSize()); + + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7); + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8); + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10); + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11); + stats.insertEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11); + + assertEquals(9, stats.size()); + assertTrue(stats.internalSize() >= 9); + + assertValues(stats, 0, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3); + assertValues(stats, 1, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4); + assertValues(stats, 2, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5); + assertValues(stats, 3, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, + ROAMING_YES, DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5); + assertValues(stats, 4, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7); + assertValues(stats, 5, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8); + assertValues(stats, 6, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10); + assertValues(stats, 7, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11); + assertValues(stats, 8, TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, + ROAMING_YES, DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11); + } + + @Test + public void testCombineExisting() throws Exception { + final NetworkStats stats = new NetworkStats(TEST_START, 10); + + stats.insertEntry(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10); + stats.insertEntry(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2); + stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L, + -128L, -1L, -1); + + assertValues(stats, 0, TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 384L, 3L, 128L, 1L, 9); + assertValues(stats, 1, TEST_IFACE, 1001, SET_DEFAULT, 0xff, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 128L, 1L, 128L, 1L, 2); + + // now try combining that should create row + stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3); + assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 128L, 1L, 128L, 1L, 3); + stats.combineValues(TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 3); + assertValues(stats, 2, TEST_IFACE, 5005, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 256L, 2L, 256L, 2L, 6); + } + + @Test + public void testSubtractIdenticalData() throws Exception { + final NetworkStats before = new NetworkStats(TEST_START, 2) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + + final NetworkStats after = new NetworkStats(TEST_START, 2) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + + final NetworkStats result = after.subtract(before); + + // identical data should result in zero delta + assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0); + assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0); + } + + @Test + public void testSubtractIdenticalRows() throws Exception { + final NetworkStats before = new NetworkStats(TEST_START, 2) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + + final NetworkStats after = new NetworkStats(TEST_START, 2) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20); + + final NetworkStats result = after.subtract(before); + + // expect delta between measurements + assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1L, 1L, 2L, 1L, 4); + assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 3L, 1L, 4L, 1L, 8); + } + + @Test + public void testSubtractNewRows() throws Exception { + final NetworkStats before = new NetworkStats(TEST_START, 2) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + + final NetworkStats after = new NetworkStats(TEST_START, 3) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12) + .insertEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20); + + final NetworkStats result = after.subtract(before); + + // its okay to have new rows + assertValues(result, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0); + assertValues(result, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0); + assertValues(result, 2, TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 20); + } + + @Test + public void testSubtractMissingRows() throws Exception { + final NetworkStats before = new NetworkStats(TEST_START, 2) + .insertEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0) + .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0); + + final NetworkStats after = new NetworkStats(TEST_START, 1) + .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0); + + final NetworkStats result = after.subtract(before); + + // should silently drop omitted rows + assertEquals(1, result.size()); + assertValues(result, 0, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 2L, 3L, 4L, 0); + assertEquals(4L, result.getTotalBytes()); + } + + @Test + public void testTotalBytes() throws Exception { + final NetworkStats iface = new NetworkStats(TEST_START, 2) + .insertEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L); + assertEquals(384L, iface.getTotalBytes()); + + final NetworkStats uidSet = new NetworkStats(TEST_START, 3) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L); + assertEquals(96L, uidSet.getTotalBytes()); + + final NetworkStats uidTag = new NetworkStats(TEST_START, 6) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L); + assertEquals(64L, uidTag.getTotalBytes()); + + final NetworkStats uidMetered = new NetworkStats(TEST_START, 3) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); + assertEquals(96L, uidMetered.getTotalBytes()); + + final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); + assertEquals(96L, uidRoaming.getTotalBytes()); + } + + @Test + public void testGroupedByIfaceEmpty() throws Exception { + final NetworkStats uidStats = new NetworkStats(TEST_START, 3); + final NetworkStats grouped = uidStats.groupedByIface(); + + assertEquals(0, uidStats.size()); + assertEquals(0, grouped.size()); + } + + @Test + public void testGroupedByIfaceAll() throws Exception { + final NetworkStats uidStats = new NetworkStats(TEST_START, 3) + .insertEntry(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) + .insertEntry(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_NO, 128L, 8L, 0L, 2L, 20L) + .insertEntry(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES, + DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L); + final NetworkStats grouped = uidStats.groupedByIface(); + + assertEquals(3, uidStats.size()); + assertEquals(1, grouped.size()); + + assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 384L, 24L, 0L, 6L, 0L); + } + + @Test + public void testGroupedByIface() throws Exception { + final NetworkStats uidStats = new NetworkStats(TEST_START, 7) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L); + + final NetworkStats grouped = uidStats.groupedByIface(); + + assertEquals(7, uidStats.size()); + + assertEquals(2, grouped.size()); + assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 384L, 24L, 0L, 2L, 0L); + assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1024L, 64L, 0L, 0L, 0L); + } + + @Test + public void testAddAllValues() { + final NetworkStats first = new NetworkStats(TEST_START, 5) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, + DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); + + final NetworkStats second = new NetworkStats(TEST_START, 2) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, + DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); + + first.combineAllValues(second); + + assertEquals(4, first.size()); + assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 0L, 0L, 0L, 0L); + assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L); + assertValues(first, 2, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, + DEFAULT_NETWORK_YES, 64L, 0L, 0L, 0L, 0L); + assertValues(first, 3, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L); + } + + @Test + public void testGetTotal() { + final NetworkStats stats = new NetworkStats(TEST_START, 7) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 512L,32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L); + + assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L); + assertValues(stats.getTotal(null, 100), 1280L, 80L, 0L, 2L, 20L); + assertValues(stats.getTotal(null, 101), 128L, 8L, 0L, 0L, 0L); + + final HashSet ifaces = Sets.newHashSet(); + assertValues(stats.getTotal(null, ifaces), 0L, 0L, 0L, 0L, 0L); + + ifaces.add(TEST_IFACE2); + assertValues(stats.getTotal(null, ifaces), 1024L, 64L, 0L, 0L, 0L); + } + + @Test + public void testRemoveUids() throws Exception { + final NetworkStats before = new NetworkStats(TEST_START, 3); + + // Test 0 item stats. + NetworkStats after = before.clone(); + after.removeUids(new int[0]); + assertEquals(0, after.size()); + after.removeUids(new int[] {100}); + assertEquals(0, after.size()); + + // Test 1 item stats. + before.insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L); + after = before.clone(); + after.removeUids(new int[0]); + assertEquals(1, after.size()); + assertValues(after, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + after.removeUids(new int[] {99}); + assertEquals(0, after.size()); + + // Append remaining test items. + before.insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L); + assertEquals(7, before.size()); + + // Test remove with empty uid list. + after = before.clone(); + after.removeUids(new int[0]); + assertValues(after.getTotalIncludingTags(null), 127L, 254L, 0L, 4L, 40L); + + // Test remove uids don't exist in stats. + after.removeUids(new int[] {98, 0, Integer.MIN_VALUE, Integer.MAX_VALUE}); + assertValues(after.getTotalIncludingTags(null), 127L, 254L, 0L, 4L, 40L); + + // Test remove all uids. + after.removeUids(new int[] {99, 100, 100, 101}); + assertEquals(0, after.size()); + + // Test remove in the middle. + after = before.clone(); + after.removeUids(new int[] {100}); + assertEquals(3, after.size()); + assertValues(after, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 32L, 4L, 0L, 0L, 0L); + assertValues(after, 2, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 64L, 2L, 0L, 0L, 0L); + } + + @Test + public void testRemoveEmptyEntries() throws Exception { + // Test empty stats. + final NetworkStats statsEmpty = new NetworkStats(TEST_START, 3); + assertEquals(0, statsEmpty.removeEmptyEntries().size()); + + // Test stats with non-zero entry. + final NetworkStats statsNonZero = new NetworkStats(TEST_START, 1) + .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + assertEquals(1, statsNonZero.size()); + final NetworkStats expectedNonZero = statsNonZero.removeEmptyEntries(); + assertEquals(1, expectedNonZero.size()); + assertValues(expectedNonZero, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L); + + // Test stats with empty entry. + final NetworkStats statsZero = new NetworkStats(TEST_START, 1) + .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + assertEquals(1, statsZero.size()); + final NetworkStats expectedZero = statsZero.removeEmptyEntries(); + assertEquals(1, statsZero.size()); // Assert immutable. + assertEquals(0, expectedZero.size()); + + // Test stats with multiple entries. + final NetworkStats statsMultiple = new NetworkStats(TEST_START, 0) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 0L, 8L, 0L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 4L, 0L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 2L, 0L) + .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 1L); + assertEquals(9, statsMultiple.size()); + final NetworkStats expectedMultiple = statsMultiple.removeEmptyEntries(); + assertEquals(9, statsMultiple.size()); // Assert immutable. + assertEquals(7, expectedMultiple.size()); + assertValues(expectedMultiple.getTotalIncludingTags(null), 14L, 104L, 4L, 4L, 21L); + + // Test stats with multiple empty entries. + assertEquals(statsMultiple.size(), statsMultiple.subtract(statsMultiple).size()); + assertEquals(0, statsMultiple.subtract(statsMultiple).removeEmptyEntries().size()); + } + + @Test + public void testClone() throws Exception { + final NetworkStats original = new NetworkStats(TEST_START, 5) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); + + // make clone and mutate original + final NetworkStats clone = original.clone(); + original.insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L); + + assertEquals(3, original.size()); + assertEquals(2, clone.size()); + + assertEquals(128L + 512L + 128L, original.getTotalBytes()); + assertEquals(128L + 512L, clone.getTotalBytes()); + } + + @Test + public void testAddWhenEmpty() throws Exception { + final NetworkStats red = new NetworkStats(TEST_START, -1); + final NetworkStats blue = new NetworkStats(TEST_START, 5) + .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) + .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); + + // We're mostly checking that we don't crash + red.combineAllValues(blue); + } + + @Test + public void testMigrateTun() throws Exception { + final int tunUid = 10030; + final String tunIface = "tun0"; + final String underlyingIface = "wlan0"; + final int testTag1 = 8888; + NetworkStats delta = new NetworkStats(TEST_START, 17) + .insertEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L) + .insertEntry(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + .insertEntry(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L) + .insertEntry(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L) + // VPN package also uses some traffic through unprotected network. + .insertEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L) + .insertEntry(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + // Tag entries + .insertEntry(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L) + .insertEntry(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L) + // Irrelevant entries + .insertEntry(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L) + // Underlying Iface entries + .insertEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L) + .insertEntry(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + .insertEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */, + 299L /* smaller than sum(tun0) */, 0L) + .insertEntry(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + + delta.migrateTun(tunUid, tunIface, Arrays.asList(underlyingIface)); + assertEquals(20, delta.size()); + + // tunIface and TEST_IFACE entries are not changed. + assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L); + assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L); + assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L); + assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L); + assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L); + assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L); + assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L); + + // Existing underlying Iface entries are updated + assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 44783L, 54L, 14178L, 62L, 0L); + assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + + // VPN underlying Iface entries are updated + assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 28304L, 27L, 1L, 2L, 0L); + assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + + // New entries are added for new application's underlying Iface traffic + assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 72667L, 197L, 43123L, 227L, 0L); + assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 9297L, 17L, 4054, 19L, 0L); + assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 21691L, 41L, 13572L, 48L, 0L); + assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 1281L, 2L, 653L, 1L, 0L); + + // New entries are added for debug purpose + assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 39605L, 46L, 12039, 51, 0); + assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 81964, 214, 47177, 246, 0); + assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, METERED_ALL, + ROAMING_ALL, DEFAULT_NETWORK_ALL, 121569, 260, 59216, 297, 0); + + } + + // Tests a case where all of the data received by the tun0 interface is echo back into the tun0 + // interface by the vpn app before it's sent out of the underlying interface. The VPN app should + // not be charged for the echoed data but it should still be charged for any extra data it sends + // via the underlying interface. + @Test + public void testMigrateTun_VpnAsLoopback() { + final int tunUid = 10030; + final String tunIface = "tun0"; + final String underlyingIface = "wlan0"; + NetworkStats delta = new NetworkStats(TEST_START, 9) + // 2 different apps sent/receive data via tun0. + .insertEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L) + .insertEntry(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L) + // VPN package resends data through the tunnel (with exaggerated overhead) + .insertEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L) + // 1 app already has some traffic on the underlying interface, the other doesn't yet + .insertEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L) + // Traffic through the underlying interface via the vpn app. + // This test should redistribute this data correctly. + .insertEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L); + + delta.migrateTun(tunUid, tunIface, Arrays.asList(underlyingIface)); + assertEquals(9, delta.size()); + + // tunIface entries should not be changed. + assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + assertValues(delta, 1, tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L); + assertValues(delta, 2, tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 240000L, 100L, 120000L, 60L, 0L); + + // Existing underlying Iface entries are updated + assertValues(delta, 3, underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 51000L, 35L, 102000L, 70L, 0L); + + // VPN underlying Iface entries are updated + assertValues(delta, 4, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 25000L, 10L, 29800L, 15L, 0L); + + // New entries are added for new application's underlying Iface traffic + assertContains(delta, underlyingIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L); + + // New entries are added for debug purpose + assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + assertContains(delta, underlyingIface, 20100, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 500, 2L, 200L, 5L, 0L); + assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE, METERED_ALL, + ROAMING_ALL, DEFAULT_NETWORK_ALL, 50500L, 27L, 100200L, 55, 0); + } + + @Test + public void testFilter_NoFilter() { + NetworkStats.Entry entry1 = new NetworkStats.Entry( + "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry2 = new NetworkStats.Entry( + "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry3 = new NetworkStats.Entry( + "test2", 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats stats = new NetworkStats(TEST_START, 3) + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3); + + stats.filter(UID_ALL, INTERFACES_ALL, TAG_ALL); + assertEquals(3, stats.size()); + assertEquals(entry1, stats.getValues(0, null)); + assertEquals(entry2, stats.getValues(1, null)); + assertEquals(entry3, stats.getValues(2, null)); + } + + @Test + public void testFilter_UidFilter() { + final int testUid = 10101; + NetworkStats.Entry entry1 = new NetworkStats.Entry( + "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry2 = new NetworkStats.Entry( + "test2", testUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry3 = new NetworkStats.Entry( + "test2", testUid, SET_DEFAULT, 123, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats stats = new NetworkStats(TEST_START, 3) + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3); + + stats.filter(testUid, INTERFACES_ALL, TAG_ALL); + assertEquals(2, stats.size()); + assertEquals(entry2, stats.getValues(0, null)); + assertEquals(entry3, stats.getValues(1, null)); + } + + @Test + public void testFilter_InterfaceFilter() { + final String testIf1 = "testif1"; + final String testIf2 = "testif2"; + NetworkStats.Entry entry1 = new NetworkStats.Entry( + testIf1, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry2 = new NetworkStats.Entry( + "otherif", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry3 = new NetworkStats.Entry( + testIf1, 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry4 = new NetworkStats.Entry( + testIf2, 10101, SET_DEFAULT, 123, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats stats = new NetworkStats(TEST_START, 4) + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3) + .insertEntry(entry4); + + stats.filter(UID_ALL, new String[] { testIf1, testIf2 }, TAG_ALL); + assertEquals(3, stats.size()); + assertEquals(entry1, stats.getValues(0, null)); + assertEquals(entry3, stats.getValues(1, null)); + assertEquals(entry4, stats.getValues(2, null)); + } + + @Test + public void testFilter_EmptyInterfaceFilter() { + NetworkStats.Entry entry1 = new NetworkStats.Entry( + "if1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry2 = new NetworkStats.Entry( + "if2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats stats = new NetworkStats(TEST_START, 3) + .insertEntry(entry1) + .insertEntry(entry2); + + stats.filter(UID_ALL, new String[] { }, TAG_ALL); + assertEquals(0, stats.size()); + } + + @Test + public void testFilter_TagFilter() { + final int testTag = 123; + final int otherTag = 456; + NetworkStats.Entry entry1 = new NetworkStats.Entry( + "test1", 10100, SET_DEFAULT, testTag, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry2 = new NetworkStats.Entry( + "test2", 10101, SET_DEFAULT, testTag, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry3 = new NetworkStats.Entry( + "test2", 10101, SET_DEFAULT, otherTag, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats stats = new NetworkStats(TEST_START, 3) + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3); + + stats.filter(UID_ALL, INTERFACES_ALL, testTag); + assertEquals(2, stats.size()); + assertEquals(entry1, stats.getValues(0, null)); + assertEquals(entry2, stats.getValues(1, null)); + } + + @Test + public void testFilterDebugEntries() { + NetworkStats.Entry entry1 = new NetworkStats.Entry( + "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry2 = new NetworkStats.Entry( + "test2", 10101, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry3 = new NetworkStats.Entry( + "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats.Entry entry4 = new NetworkStats.Entry( + "test2", 10101, SET_DBG_VPN_OUT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); + + NetworkStats stats = new NetworkStats(TEST_START, 4) + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3) + .insertEntry(entry4); + + stats.filterDebugEntries(); + + assertEquals(2, stats.size()); + assertEquals(entry1, stats.getValues(0, null)); + assertEquals(entry3, stats.getValues(1, null)); + } + + @Test + public void testApply464xlatAdjustments() { + final String v4Iface = "v4-wlan0"; + final String baseIface = "wlan0"; + final String otherIface = "other"; + final int appUid = 10001; + final int rootUid = Process.ROOT_UID; + ArrayMap stackedIface = new ArrayMap<>(); + stackedIface.put(v4Iface, baseIface); + + // Ipv4 traffic sent/received by an app on stacked interface. + final NetworkStats.Entry appEntry = new NetworkStats.Entry( + v4Iface, appUid, SET_DEFAULT, TAG_NONE, + 30501490 /* rxBytes */, + 22401 /* rxPackets */, + 876235 /* txBytes */, + 13805 /* txPackets */, + 0 /* operations */); + + // Traffic measured for the root uid on the base interface. + final NetworkStats.Entry rootUidEntry = new NetworkStats.Entry( + baseIface, rootUid, SET_DEFAULT, TAG_NONE, + 163577 /* rxBytes */, + 187 /* rxPackets */, + 17607 /* txBytes */, + 97 /* txPackets */, + 0 /* operations */); + + final NetworkStats.Entry otherEntry = new NetworkStats.Entry( + otherIface, appUid, SET_DEFAULT, TAG_NONE, + 2600 /* rxBytes */, + 2 /* rxPackets */, + 3800 /* txBytes */, + 3 /* txPackets */, + 0 /* operations */); + + final NetworkStats stats = new NetworkStats(TEST_START, 3) + .insertEntry(appEntry) + .insertEntry(rootUidEntry) + .insertEntry(otherEntry); + + stats.apply464xlatAdjustments(stackedIface); + + assertEquals(3, stats.size()); + final NetworkStats.Entry expectedAppUid = new NetworkStats.Entry( + v4Iface, appUid, SET_DEFAULT, TAG_NONE, + 30949510, + 22401, + 1152335, + 13805, + 0); + final NetworkStats.Entry expectedRootUid = new NetworkStats.Entry( + baseIface, 0, SET_DEFAULT, TAG_NONE, + 163577, + 187, + 17607, + 97, + 0); + assertEquals(expectedAppUid, stats.getValues(0, null)); + assertEquals(expectedRootUid, stats.getValues(1, null)); + assertEquals(otherEntry, stats.getValues(2, null)); + } + + @Test + public void testApply464xlatAdjustments_noStackedIface() { + NetworkStats.Entry firstEntry = new NetworkStats.Entry( + "if1", 10002, SET_DEFAULT, TAG_NONE, + 2600 /* rxBytes */, + 2 /* rxPackets */, + 3800 /* txBytes */, + 3 /* txPackets */, + 0 /* operations */); + NetworkStats.Entry secondEntry = new NetworkStats.Entry( + "if2", 10002, SET_DEFAULT, TAG_NONE, + 5000 /* rxBytes */, + 3 /* rxPackets */, + 6000 /* txBytes */, + 4 /* txPackets */, + 0 /* operations */); + + NetworkStats stats = new NetworkStats(TEST_START, 2) + .insertEntry(firstEntry) + .insertEntry(secondEntry); + + // Empty map: no adjustment + stats.apply464xlatAdjustments(new ArrayMap<>()); + + assertEquals(2, stats.size()); + assertEquals(firstEntry, stats.getValues(0, null)); + assertEquals(secondEntry, stats.getValues(1, null)); + } + + private static void assertContains(NetworkStats stats, String iface, int uid, int set, + int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, + long txBytes, long txPackets, long operations) { + int index = stats.findIndex(iface, uid, set, tag, metered, roaming, defaultNetwork); + assertTrue(index != -1); + assertValues(stats, index, iface, uid, set, tag, metered, roaming, defaultNetwork, + rxBytes, rxPackets, txBytes, txPackets, operations); + } + + private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set, + int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, + long txBytes, long txPackets, long operations) { + final NetworkStats.Entry entry = stats.getValues(index, null); + assertValues(entry, iface, uid, set, tag, metered, roaming, defaultNetwork); + assertValues(entry, rxBytes, rxPackets, txBytes, txPackets, operations); + } + + private static void assertValues( + NetworkStats.Entry entry, String iface, int uid, int set, int tag, int metered, + int roaming, int defaultNetwork) { + assertEquals(iface, entry.iface); + assertEquals(uid, entry.uid); + assertEquals(set, entry.set); + assertEquals(tag, entry.tag); + assertEquals(metered, entry.metered); + assertEquals(roaming, entry.roaming); + assertEquals(defaultNetwork, entry.defaultNetwork); + } + + private static void assertValues(NetworkStats.Entry entry, long rxBytes, long rxPackets, + long txBytes, long txPackets, long operations) { + assertEquals(rxBytes, entry.rxBytes); + assertEquals(rxPackets, entry.rxPackets); + assertEquals(txBytes, entry.txBytes); + assertEquals(txPackets, entry.txPackets); + assertEquals(operations, entry.operations); + } + +} diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt new file mode 100644 index 0000000000..ab6b2f4098 --- /dev/null +++ b/tests/unit/java/android/net/NetworkTemplateTest.kt @@ -0,0 +1,351 @@ +/* + * 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 android.net + +import android.content.Context +import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_WIFI +import android.net.NetworkIdentity.SUBTYPE_COMBINED +import android.net.NetworkIdentity.OEM_NONE +import android.net.NetworkIdentity.OEM_PAID +import android.net.NetworkIdentity.OEM_PRIVATE +import android.net.NetworkIdentity.buildNetworkIdentity +import android.net.NetworkStats.DEFAULT_NETWORK_ALL +import android.net.NetworkStats.METERED_ALL +import android.net.NetworkStats.ROAMING_ALL +import android.net.NetworkTemplate.MATCH_MOBILE +import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD +import android.net.NetworkTemplate.MATCH_WIFI +import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD +import android.net.NetworkTemplate.WIFI_NETWORKID_ALL +import android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA +import android.net.NetworkTemplate.NETWORK_TYPE_ALL +import android.net.NetworkTemplate.OEM_MANAGED_ALL +import android.net.NetworkTemplate.OEM_MANAGED_NO +import android.net.NetworkTemplate.OEM_MANAGED_YES +import android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT +import android.net.NetworkTemplate.buildTemplateWifi +import android.net.NetworkTemplate.buildTemplateWifiWildcard +import android.net.NetworkTemplate.buildTemplateCarrier +import android.net.NetworkTemplate.buildTemplateMobileWithRatType +import android.telephony.TelephonyManager +import com.android.testutils.assertParcelSane +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import kotlin.test.assertTrue + +private const val TEST_IMSI1 = "imsi1" +private const val TEST_IMSI2 = "imsi2" +private const val TEST_SSID1 = "ssid1" +private const val TEST_SSID2 = "ssid2" + +@RunWith(JUnit4::class) +class NetworkTemplateTest { + private val mockContext = mock(Context::class.java) + + private fun buildMobileNetworkState(subscriberId: String): NetworkStateSnapshot = + buildNetworkState(TYPE_MOBILE, subscriberId = subscriberId) + private fun buildWifiNetworkState(subscriberId: String?, ssid: String?): NetworkStateSnapshot = + buildNetworkState(TYPE_WIFI, subscriberId = subscriberId, ssid = ssid) + + private fun buildNetworkState( + type: Int, + subscriberId: String? = null, + ssid: String? = null, + oemManaged: Int = OEM_NONE + ): NetworkStateSnapshot { + val lp = LinkProperties() + val caps = NetworkCapabilities().apply { + setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false) + setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true) + setSSID(ssid) + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID, + (oemManaged and OEM_PAID) == OEM_PAID) + setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, + (oemManaged and OEM_PRIVATE) == OEM_PRIVATE) + } + return NetworkStateSnapshot(mock(Network::class.java), caps, lp, subscriberId, type) + } + + private fun NetworkTemplate.assertMatches(ident: NetworkIdentity) = + assertTrue(matches(ident), "$this does not match $ident") + + private fun NetworkTemplate.assertDoesNotMatch(ident: NetworkIdentity) = + assertFalse(matches(ident), "$this should match $ident") + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun testWifiWildcardMatches() { + val templateWifiWildcard = buildTemplateWifiWildcard() + + val identMobileImsi1 = buildNetworkIdentity(mockContext, + buildMobileNetworkState(TEST_IMSI1), + false, TelephonyManager.NETWORK_TYPE_UMTS) + val identWifiImsiNullSsid1 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0) + val identWifiImsi1Ssid1 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0) + + templateWifiWildcard.assertDoesNotMatch(identMobileImsi1) + templateWifiWildcard.assertMatches(identWifiImsiNullSsid1) + templateWifiWildcard.assertMatches(identWifiImsi1Ssid1) + } + + @Test + fun testWifiMatches() { + val templateWifiSsid1 = buildTemplateWifi(TEST_SSID1) + val templateWifiSsid1ImsiNull = buildTemplateWifi(TEST_SSID1, null) + val templateWifiSsid1Imsi1 = buildTemplateWifi(TEST_SSID1, TEST_IMSI1) + val templateWifiSsidAllImsi1 = buildTemplateWifi(WIFI_NETWORKID_ALL, TEST_IMSI1) + + val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1), + false, TelephonyManager.NETWORK_TYPE_UMTS) + val identWifiImsiNullSsid1 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0) + val identWifiImsi1Ssid1 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0) + val identWifiImsi2Ssid1 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_SSID1), true, 0) + val identWifiImsi1Ssid2 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID2), true, 0) + + // Verify that template with SSID only matches any subscriberId and specific SSID. + templateWifiSsid1.assertDoesNotMatch(identMobile1) + templateWifiSsid1.assertMatches(identWifiImsiNullSsid1) + templateWifiSsid1.assertMatches(identWifiImsi1Ssid1) + templateWifiSsid1.assertMatches(identWifiImsi2Ssid1) + templateWifiSsid1.assertDoesNotMatch(identWifiImsi1Ssid2) + + // Verify that template with SSID1 and null imsi matches any network with + // SSID1 and null imsi. + templateWifiSsid1ImsiNull.assertDoesNotMatch(identMobile1) + templateWifiSsid1ImsiNull.assertMatches(identWifiImsiNullSsid1) + templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi1Ssid1) + templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi2Ssid1) + templateWifiSsid1ImsiNull.assertDoesNotMatch(identWifiImsi1Ssid2) + + // Verify that template with SSID1 and imsi1 matches any network with + // SSID1 and imsi1. + templateWifiSsid1Imsi1.assertDoesNotMatch(identMobile1) + templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsiNullSsid1) + templateWifiSsid1Imsi1.assertMatches(identWifiImsi1Ssid1) + templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsi2Ssid1) + templateWifiSsid1Imsi1.assertDoesNotMatch(identWifiImsi1Ssid2) + + // Verify that template with SSID all and imsi1 matches any network with + // any SSID and imsi1. + templateWifiSsidAllImsi1.assertDoesNotMatch(identMobile1) + templateWifiSsidAllImsi1.assertDoesNotMatch(identWifiImsiNullSsid1) + templateWifiSsidAllImsi1.assertMatches(identWifiImsi1Ssid1) + templateWifiSsidAllImsi1.assertDoesNotMatch(identWifiImsi2Ssid1) + templateWifiSsidAllImsi1.assertMatches(identWifiImsi1Ssid2) + } + + @Test + fun testCarrierMatches() { + val templateCarrierImsi1 = buildTemplateCarrier(TEST_IMSI1) + + val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1), + false, TelephonyManager.NETWORK_TYPE_UMTS) + val identMobile2 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI2), + false, TelephonyManager.NETWORK_TYPE_UMTS) + val identWifiSsid1 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0) + val identCarrierWifiImsi1 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_SSID1), true, 0) + val identCarrierWifiImsi2 = buildNetworkIdentity( + mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_SSID1), true, 0) + + templateCarrierImsi1.assertMatches(identCarrierWifiImsi1) + templateCarrierImsi1.assertDoesNotMatch(identCarrierWifiImsi2) + templateCarrierImsi1.assertDoesNotMatch(identWifiSsid1) + templateCarrierImsi1.assertMatches(identMobile1) + templateCarrierImsi1.assertDoesNotMatch(identMobile2) + } + + @Test + fun testRatTypeGroupMatches() { + val stateMobile = buildMobileNetworkState(TEST_IMSI1) + // Build UMTS template that matches mobile identities with RAT in the same + // group with any IMSI. See {@link NetworkTemplate#getCollapsedRatType}. + val templateUmts = buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS) + // Build normal template that matches mobile identities with any RAT and IMSI. + val templateAll = buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL) + // Build template with UNKNOWN RAT that matches mobile identities with RAT that + // cannot be determined. + val templateUnknown = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UNKNOWN) + + val identUmts = buildNetworkIdentity( + mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_UMTS) + val identHsdpa = buildNetworkIdentity( + mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_HSDPA) + val identLte = buildNetworkIdentity( + mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_LTE) + val identCombined = buildNetworkIdentity( + mockContext, stateMobile, false, SUBTYPE_COMBINED) + val identImsi2 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI2), + false, TelephonyManager.NETWORK_TYPE_UMTS) + val identWifi = buildNetworkIdentity( + mockContext, buildWifiNetworkState(null, TEST_SSID1), true, 0) + + // Assert that identity with the same RAT matches. + templateUmts.assertMatches(identUmts) + templateAll.assertMatches(identUmts) + templateUnknown.assertDoesNotMatch(identUmts) + // Assert that identity with the RAT within the same group matches. + templateUmts.assertMatches(identHsdpa) + templateAll.assertMatches(identHsdpa) + templateUnknown.assertDoesNotMatch(identHsdpa) + // Assert that identity with the RAT out of the same group only matches template with + // NETWORK_TYPE_ALL. + templateUmts.assertDoesNotMatch(identLte) + templateAll.assertMatches(identLte) + templateUnknown.assertDoesNotMatch(identLte) + // Assert that identity with combined RAT only matches with template with NETWORK_TYPE_ALL + // and NETWORK_TYPE_UNKNOWN. + templateUmts.assertDoesNotMatch(identCombined) + templateAll.assertMatches(identCombined) + templateUnknown.assertMatches(identCombined) + // Assert that identity with different IMSI matches. + templateUmts.assertMatches(identImsi2) + templateAll.assertMatches(identImsi2) + templateUnknown.assertDoesNotMatch(identImsi2) + // Assert that wifi identity does not match. + templateUmts.assertDoesNotMatch(identWifi) + templateAll.assertDoesNotMatch(identWifi) + templateUnknown.assertDoesNotMatch(identWifi) + } + + @Test + fun testParcelUnparcel() { + val templateMobile = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1, null, null, METERED_ALL, + ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_LTE, + OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT) + val templateWifi = NetworkTemplate(MATCH_WIFI, null, null, TEST_SSID1, METERED_ALL, + ROAMING_ALL, DEFAULT_NETWORK_ALL, 0, OEM_MANAGED_ALL, + SUBSCRIBER_ID_MATCH_RULE_EXACT) + val templateOem = NetworkTemplate(MATCH_MOBILE, null, null, null, METERED_ALL, + ROAMING_ALL, DEFAULT_NETWORK_ALL, 0, OEM_MANAGED_YES, + SUBSCRIBER_ID_MATCH_RULE_EXACT) + assertParcelSane(templateMobile, 10) + assertParcelSane(templateWifi, 10) + assertParcelSane(templateOem, 10) + } + + // Verify NETWORK_TYPE_* constants in NetworkTemplate do not conflict with + // TelephonyManager#NETWORK_TYPE_* constants. + @Test + fun testNetworkTypeConstants() { + for (ratType in TelephonyManager.getAllNetworkTypes()) { + assertNotEquals(NETWORK_TYPE_ALL, ratType) + assertNotEquals(NETWORK_TYPE_5G_NSA, ratType) + } + } + + @Test + fun testOemNetworkConstants() { + val constantValues = arrayOf(OEM_MANAGED_YES, OEM_MANAGED_ALL, OEM_MANAGED_NO, + OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE) + + // Verify that "not OEM managed network" constants are equal. + assertEquals(OEM_MANAGED_NO, OEM_NONE) + + // Verify the constants don't conflict. + assertEquals(constantValues.size, constantValues.distinct().count()) + } + + /** + * Helper to enumerate and assert OEM managed wifi and mobile {@code NetworkTemplate}s match + * their the appropriate OEM managed {@code NetworkIdentity}s. + * + * @param networkType {@code TYPE_MOBILE} or {@code TYPE_WIFI} + * @param matchType A match rule from {@code NetworkTemplate.MATCH_*} corresponding to the + * networkType. + * @param subscriberId To be populated with {@code TEST_IMSI*} only if networkType is + * {@code TYPE_MOBILE}. May be left as null when matchType is + * {@link NetworkTemplate.MATCH_MOBILE_WILDCARD}. + * @param templateSsid Top be populated with {@code TEST_SSID*} only if networkType is + * {@code TYPE_WIFI}. May be left as null when matchType is + * {@link NetworkTemplate.MATCH_WIFI_WILDCARD}. + * @param identSsid If networkType is {@code TYPE_WIFI}, this value must *NOT* be null. Provide + * one of {@code TEST_SSID*}. + */ + private fun matchOemManagedIdent( + networkType: Int, + matchType: Int, + subscriberId: String? = null, + templateSsid: String? = null, + identSsid: String? = null + ) { + val oemManagedStates = arrayOf(OEM_NONE, OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE) + val matchSubscriberIds = arrayOf(subscriberId) + + val templateOemYes = NetworkTemplate(matchType, subscriberId, matchSubscriberIds, + templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, + OEM_MANAGED_YES, SUBSCRIBER_ID_MATCH_RULE_EXACT) + val templateOemAll = NetworkTemplate(matchType, subscriberId, matchSubscriberIds, + templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, + OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT) + + for (identityOemManagedState in oemManagedStates) { + val ident = buildNetworkIdentity(mockContext, buildNetworkState(networkType, + subscriberId, identSsid, identityOemManagedState), /*defaultNetwork=*/false, + /*subType=*/0) + + // Create a template with each OEM managed type and match it against the NetworkIdentity + for (templateOemManagedState in oemManagedStates) { + val template = NetworkTemplate(matchType, subscriberId, matchSubscriberIds, + templateSsid, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, + NETWORK_TYPE_ALL, templateOemManagedState, SUBSCRIBER_ID_MATCH_RULE_EXACT) + if (identityOemManagedState == templateOemManagedState) { + template.assertMatches(ident) + } else { + template.assertDoesNotMatch(ident) + } + } + // OEM_MANAGED_ALL ignores OEM state. + templateOemAll.assertMatches(ident) + if (identityOemManagedState == OEM_NONE) { + // OEM_MANAGED_YES matches everything except OEM_NONE. + templateOemYes.assertDoesNotMatch(ident) + } else { + templateOemYes.assertMatches(ident) + } + } + } + + @Test + fun testOemManagedMatchesIdent() { + matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE, subscriberId = TEST_IMSI1) + matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE_WILDCARD) + matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI, templateSsid = TEST_SSID1, + identSsid = TEST_SSID1) + matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI_WILDCARD, identSsid = TEST_SSID1) + } +} diff --git a/tests/unit/java/android/net/NetworkUtilsTest.java b/tests/unit/java/android/net/NetworkUtilsTest.java new file mode 100644 index 0000000000..7748288aeb --- /dev/null +++ b/tests/unit/java/android/net/NetworkUtilsTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 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; + +import static junit.framework.Assert.assertEquals; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.math.BigInteger; +import java.util.TreeSet; + +@RunWith(AndroidJUnit4.class) +@androidx.test.filters.SmallTest +public class NetworkUtilsTest { + @Test + public void testRoutedIPv4AddressCount() { + final TreeSet set = new TreeSet<>(IpPrefix.lengthComparator()); + // No routes routes to no addresses. + assertEquals(0, NetworkUtils.routedIPv4AddressCount(set)); + + set.add(new IpPrefix("0.0.0.0/0")); + assertEquals(1l << 32, NetworkUtils.routedIPv4AddressCount(set)); + + set.add(new IpPrefix("20.18.0.0/16")); + set.add(new IpPrefix("20.18.0.0/24")); + set.add(new IpPrefix("20.18.0.0/8")); + // There is a default route, still covers everything + assertEquals(1l << 32, NetworkUtils.routedIPv4AddressCount(set)); + + set.clear(); + set.add(new IpPrefix("20.18.0.0/24")); + set.add(new IpPrefix("20.18.0.0/8")); + // The 8-length includes the 24-length prefix + assertEquals(1l << 24, NetworkUtils.routedIPv4AddressCount(set)); + + set.add(new IpPrefix("10.10.10.126/25")); + // The 8-length does not include this 25-length prefix + assertEquals((1l << 24) + (1 << 7), NetworkUtils.routedIPv4AddressCount(set)); + + set.clear(); + set.add(new IpPrefix("1.2.3.4/32")); + set.add(new IpPrefix("1.2.3.4/32")); + set.add(new IpPrefix("1.2.3.4/32")); + set.add(new IpPrefix("1.2.3.4/32")); + assertEquals(1l, NetworkUtils.routedIPv4AddressCount(set)); + + set.add(new IpPrefix("1.2.3.5/32")); + set.add(new IpPrefix("1.2.3.6/32")); + + set.add(new IpPrefix("1.2.3.7/32")); + set.add(new IpPrefix("1.2.3.8/32")); + set.add(new IpPrefix("1.2.3.9/32")); + set.add(new IpPrefix("1.2.3.0/32")); + assertEquals(7l, NetworkUtils.routedIPv4AddressCount(set)); + + // 1.2.3.4/30 eats 1.2.3.{4-7}/32 + set.add(new IpPrefix("1.2.3.4/30")); + set.add(new IpPrefix("6.2.3.4/28")); + set.add(new IpPrefix("120.2.3.4/16")); + assertEquals(7l - 4 + 4 + 16 + 65536, NetworkUtils.routedIPv4AddressCount(set)); + } + + @Test + public void testRoutedIPv6AddressCount() { + final TreeSet set = new TreeSet<>(IpPrefix.lengthComparator()); + // No routes routes to no addresses. + assertEquals(BigInteger.ZERO, NetworkUtils.routedIPv6AddressCount(set)); + + set.add(new IpPrefix("::/0")); + assertEquals(BigInteger.ONE.shiftLeft(128), NetworkUtils.routedIPv6AddressCount(set)); + + set.add(new IpPrefix("1234:622a::18/64")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/96")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/8")); + // There is a default route, still covers everything + assertEquals(BigInteger.ONE.shiftLeft(128), NetworkUtils.routedIPv6AddressCount(set)); + + set.clear(); + set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/96")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6adb/8")); + // The 8-length includes the 96-length prefix + assertEquals(BigInteger.ONE.shiftLeft(120), NetworkUtils.routedIPv6AddressCount(set)); + + set.add(new IpPrefix("10::26/64")); + // The 8-length does not include this 64-length prefix + assertEquals(BigInteger.ONE.shiftLeft(120).add(BigInteger.ONE.shiftLeft(64)), + NetworkUtils.routedIPv6AddressCount(set)); + + set.clear(); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/128")); + assertEquals(BigInteger.ONE, NetworkUtils.routedIPv6AddressCount(set)); + + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad5/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad6/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad7/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad8/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad9/128")); + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad0/128")); + assertEquals(BigInteger.valueOf(7), NetworkUtils.routedIPv6AddressCount(set)); + + // add4:f00:80:f7:1111::6ad4/126 eats add4:f00:8[:f7:1111::6ad{4-7}/128 + set.add(new IpPrefix("add4:f00:80:f7:1111::6ad4/126")); + set.add(new IpPrefix("d00d:f00:80:f7:1111::6ade/124")); + set.add(new IpPrefix("f00b:a33::/112")); + assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536), + NetworkUtils.routedIPv6AddressCount(set)); + } +} diff --git a/tests/unit/java/android/net/QosSocketFilterTest.java b/tests/unit/java/android/net/QosSocketFilterTest.java new file mode 100644 index 0000000000..ad58960eaa --- /dev/null +++ b/tests/unit/java/android/net/QosSocketFilterTest.java @@ -0,0 +1,75 @@ +/* + * 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 android.net; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.net.InetSocketAddress; + +@RunWith(AndroidJUnit4.class) +@androidx.test.filters.SmallTest +public class QosSocketFilterTest { + + @Test + public void testPortExactMatch() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4"); + assertTrue(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 10), addressB, 10, 10)); + + } + + @Test + public void testPortLessThanStart() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4"); + assertFalse(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 8), addressB, 10, 10)); + } + + @Test + public void testPortGreaterThanEnd() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4"); + assertFalse(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 18), addressB, 10, 10)); + } + + @Test + public void testPortBetweenStartAndEnd() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4"); + assertTrue(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 10), addressB, 8, 18)); + } + + @Test + public void testAddressesDontMatch() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.5"); + assertFalse(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 10), addressB, 10, 10)); + } +} + diff --git a/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java new file mode 100644 index 0000000000..6714bb1abb --- /dev/null +++ b/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 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; + +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.net.wifi.WifiNetworkSpecifier; +import android.telephony.SubscriptionManager; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +/** + * Unit test for {@link android.net.TelephonyNetworkSpecifier}. + */ +@SmallTest +public class TelephonyNetworkSpecifierTest { + private static final int TEST_SUBID = 5; + private static final String TEST_SSID = "Test123"; + + /** + * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier + * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}. + */ + @Test + public void testBuilderBuildWithDefault() { + try { + new TelephonyNetworkSpecifier.Builder().build(); + } catch (IllegalArgumentException iae) { + // expected, test pass + } + } + + /** + * Validate that no exception will be thrown even if pass invalid subscription id to + * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}. + */ + @Test + public void testBuilderBuildWithInvalidSubId() { + TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID) + .build(); + assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID); + } + + /** + * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId. + */ + @Test + public void testBuilderBuildWithValidSubId() { + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + assertEquals(TEST_SUBID, specifier.getSubscriptionId()); + } + + /** + * Validate that parcel marshalling/unmarshalling works. + */ + @Test + public void testParcel() { + TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + assertParcelSane(specifier, 1 /* fieldCount */); + } + + /** + * Validate the behavior of method canBeSatisfiedBy(). + */ + @Test + public void testCanBeSatisfiedBy() { + final TelephonyNetworkSpecifier tns1 = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + final TelephonyNetworkSpecifier tns2 = new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(TEST_SUBID) + .build(); + final WifiNetworkSpecifier wns = new WifiNetworkSpecifier.Builder() + .setSsid(TEST_SSID) + .build(); + final MatchAllNetworkSpecifier mans = new MatchAllNetworkSpecifier(); + + // Test equality + assertEquals(tns1, tns2); + assertTrue(tns1.canBeSatisfiedBy(tns1)); + assertTrue(tns1.canBeSatisfiedBy(tns2)); + + // Test other edge cases. + assertFalse(tns1.canBeSatisfiedBy(null)); + assertFalse(tns1.canBeSatisfiedBy(wns)); + assertTrue(tns1.canBeSatisfiedBy(mans)); + } +} diff --git a/tests/unit/java/android/net/VpnManagerTest.java b/tests/unit/java/android/net/VpnManagerTest.java new file mode 100644 index 0000000000..3135062138 --- /dev/null +++ b/tests/unit/java/android/net/VpnManagerTest.java @@ -0,0 +1,138 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Intent; +import android.test.mock.MockContext; +import android.util.SparseArray; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.net.VpnProfile; +import com.android.internal.util.MessageUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link VpnManager}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VpnManagerTest { + private static final String PKG_NAME = "fooPackage"; + + private static final String SESSION_NAME_STRING = "testSession"; + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); + + private IVpnManager mMockService; + private VpnManager mVpnManager; + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return PKG_NAME; + } + }; + + @Before + public void setUp() throws Exception { + mMockService = mock(IVpnManager.class); + mVpnManager = new VpnManager(mMockContext, mMockService); + } + + @Test + public void testProvisionVpnProfilePreconsented() throws Exception { + final PlatformVpnProfile profile = getPlatformVpnProfile(); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(true); + + // Expect there to be no intent returned, as consent has already been granted. + assertNull(mVpnManager.provisionVpnProfile(profile)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + } + + @Test + public void testProvisionVpnProfileNeedsConsent() throws Exception { + final PlatformVpnProfile profile = getPlatformVpnProfile(); + when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))) + .thenReturn(false); + + // Expect intent to be returned, as consent has not already been granted. + final Intent intent = mVpnManager.provisionVpnProfile(profile); + assertNotNull(intent); + + final ComponentName expectedComponentName = + ComponentName.unflattenFromString( + "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog"); + assertEquals(expectedComponentName, intent.getComponent()); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + } + + @Test + public void testDeleteProvisionedVpnProfile() throws Exception { + mVpnManager.deleteProvisionedVpnProfile(); + verify(mMockService).deleteVpnProfile(eq(PKG_NAME)); + } + + @Test + public void testStartProvisionedVpnProfile() throws Exception { + mVpnManager.startProvisionedVpnProfile(); + verify(mMockService).startVpnProfile(eq(PKG_NAME)); + } + + @Test + public void testStopProvisionedVpnProfile() throws Exception { + mVpnManager.stopProvisionedVpnProfile(); + verify(mMockService).stopVpnProfile(eq(PKG_NAME)); + } + + private Ikev2VpnProfile getPlatformVpnProfile() throws Exception { + return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING) + .setBypassable(true) + .setMaxMtu(1300) + .setMetered(true) + .setAuthPsk(PSK_BYTES) + .build(); + } + + @Test + public void testVpnTypesEqual() throws Exception { + SparseArray vmVpnTypes = MessageUtils.findMessageNames( + new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" }); + SparseArray nativeVpnType = MessageUtils.findMessageNames( + new Class[] { NativeVpnType.class }, new String[]{ "" }); + + // TYPE_VPN_NONE = -1 is only defined in VpnManager. + assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size()); + for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) { + assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i)); + } + } +} diff --git a/tests/unit/java/android/net/VpnTransportInfoTest.java b/tests/unit/java/android/net/VpnTransportInfoTest.java new file mode 100644 index 0000000000..ccaa5cf7e9 --- /dev/null +++ b/tests/unit/java/android/net/VpnTransportInfoTest.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 android.net; + +import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; +import static android.net.NetworkCapabilities.REDACT_NONE; + +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VpnTransportInfoTest { + + @Test + public void testParceling() { + VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345"); + assertParcelSane(v, 2 /* fieldCount */); + } + + @Test + public void testEqualsAndHashCode() { + String session1 = "12345"; + String session2 = "6789"; + VpnTransportInfo v11 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, session1); + VpnTransportInfo v12 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE, session1); + VpnTransportInfo v13 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, session1); + VpnTransportInfo v14 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY, session1); + VpnTransportInfo v15 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM, session1); + VpnTransportInfo v21 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY, session2); + + VpnTransportInfo v31 = v11.makeCopy(REDACT_FOR_NETWORK_SETTINGS); + VpnTransportInfo v32 = v13.makeCopy(REDACT_FOR_NETWORK_SETTINGS); + + assertNotEquals(v11, v12); + assertNotEquals(v13, v14); + assertNotEquals(v14, v15); + assertNotEquals(v14, v21); + + assertEquals(v11, v13); + assertEquals(v31, v32); + assertEquals(v11.hashCode(), v13.hashCode()); + assertEquals(REDACT_FOR_NETWORK_SETTINGS, v32.getApplicableRedactions()); + assertEquals(session1, v15.makeCopy(REDACT_NONE).getSessionId()); + } +} diff --git a/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java b/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java new file mode 100644 index 0000000000..603c875195 --- /dev/null +++ b/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018 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.ipmemorystore; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk; +import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirkParcelable; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Modifier; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collections; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ParcelableTests { + @Test + public void testNetworkAttributesParceling() throws Exception { + final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); + NetworkAttributes in = builder.build(); + assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4")); + // lease will expire in two hours + builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000); + // cluster stays null this time around + builder.setDnsAddresses(Collections.emptyList()); + builder.setMtu(18); + in = builder.build(); + assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9")); + builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000); + builder.setCluster("groupHint"); + builder.setDnsAddresses(Arrays.asList( + InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"), + InetAddress.getByName("6.7.8.9"))); + builder.setMtu(1_000_000); + in = builder.build(); + assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + builder.setMtu(null); + in = builder.build(); + assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + // Verify that this test does not miss any new field added later. + // If any field is added to NetworkAttributes it must be tested here for parceling + // roundtrip. + assertEquals(6, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); + } + + @Test + public void testPrivateDataParceling() throws Exception { + final Blob in = new Blob(); + in.data = new byte[] {89, 111, 108, 111}; + final Blob out = parcelingRoundTrip(in); + // Object.equals on byte[] tests the references + assertEquals(in.data.length, out.data.length); + assertTrue(Arrays.equals(in.data, out.data)); + } + + @Test + public void testSameL3NetworkResponseParceling() throws Exception { + final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable(); + parcelable.l2Key1 = "key 1"; + parcelable.l2Key2 = "key 2"; + parcelable.confidence = 0.43f; + + final SameL3NetworkResponse in = new SameL3NetworkResponse(parcelable); + assertEquals("key 1", in.l2Key1); + assertEquals("key 2", in.l2Key2); + assertEquals(0.43f, in.confidence, 0.01f /* delta */); + + final SameL3NetworkResponse out = + new SameL3NetworkResponse(parcelingRoundTrip(in.toParcelable())); + + assertEquals(in, out); + assertEquals(in.l2Key1, out.l2Key1); + assertEquals(in.l2Key2, out.l2Key2); + assertEquals(in.confidence, out.confidence, 0.01f /* delta */); + } + + @Test + public void testIPv6ProvisioningLossQuirkParceling() throws Exception { + final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); + final IPv6ProvisioningLossQuirkParcelable parcelable = + new IPv6ProvisioningLossQuirkParcelable(); + final long expiry = System.currentTimeMillis() + 7_200_000; + + parcelable.detectionCount = 3; + parcelable.quirkExpiry = expiry; // quirk info will expire in two hours + builder.setIpv6ProvLossQuirk(IPv6ProvisioningLossQuirk.fromStableParcelable(parcelable)); + final NetworkAttributes in = builder.build(); + + final NetworkAttributes out = new NetworkAttributes(parcelingRoundTrip(in.toParcelable())); + assertEquals(out.ipv6ProvisioningLossQuirk, in.ipv6ProvisioningLossQuirk); + } + + private T parcelingRoundTrip(final T in) throws Exception { + final Parcel p = Parcel.obtain(); + in.writeToParcel(p, /* flags */ 0); + p.setDataPosition(0); + final byte[] marshalledData = p.marshall(); + p.recycle(); + + final Parcel q = Parcel.obtain(); + q.unmarshall(marshalledData, 0, marshalledData.length); + q.setDataPosition(0); + + final Parcelable.Creator creator = (Parcelable.Creator) + in.getClass().getField("CREATOR").get(null); // static object, so null receiver + final T unmarshalled = (T) creator.createFromParcel(q); + q.recycle(); + return unmarshalled; + } +} diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java new file mode 100644 index 0000000000..b0a9b8a553 --- /dev/null +++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2017 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.nsd; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.AsyncChannel; +import com.android.testutils.HandlerUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NsdManagerTest { + + static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD; + + @Mock Context mContext; + @Mock INsdManager mService; + MockServiceHandler mServiceHandler; + + NsdManager mManager; + + long mTimeoutMs = 200; // non-final so that tests can adjust the value. + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mServiceHandler = spy(MockServiceHandler.create(mContext)); + when(mService.getMessenger()).thenReturn(new Messenger(mServiceHandler)); + + mManager = makeManager(); + } + + @After + public void tearDown() throws Exception { + HandlerUtils.waitForIdle(mServiceHandler, mTimeoutMs); + mServiceHandler.chan.disconnect(); + mServiceHandler.stop(); + if (mManager != null) { + mManager.disconnect(); + } + } + + @Test + public void testResolveService() { + NsdManager manager = mManager; + + NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type"); + NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type"); + NsdManager.ResolveListener listener = mock(NsdManager.ResolveListener.class); + + manager.resolveService(request, listener); + int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE); + int err = 33; + sendResponse(NsdManager.RESOLVE_SERVICE_FAILED, err, key1, null); + verify(listener, timeout(mTimeoutMs).times(1)).onResolveFailed(request, err); + + manager.resolveService(request, listener); + int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE); + sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply); + verify(listener, timeout(mTimeoutMs).times(1)).onServiceResolved(reply); + } + + @Test + public void testParallelResolveService() { + NsdManager manager = mManager; + + NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type"); + NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type"); + + NsdManager.ResolveListener listener1 = mock(NsdManager.ResolveListener.class); + NsdManager.ResolveListener listener2 = mock(NsdManager.ResolveListener.class); + + manager.resolveService(request, listener1); + int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE); + + manager.resolveService(request, listener2); + int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE); + + sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply); + sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key1, reply); + + verify(listener1, timeout(mTimeoutMs).times(1)).onServiceResolved(reply); + verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply); + } + + @Test + public void testRegisterService() { + NsdManager manager = mManager; + + NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type"); + NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type"); + request1.setPort(2201); + request2.setPort(2202); + NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class); + NsdManager.RegistrationListener listener2 = mock(NsdManager.RegistrationListener.class); + + // Register two services + manager.registerService(request1, PROTOCOL, listener1); + int key1 = verifyRequest(NsdManager.REGISTER_SERVICE); + + manager.registerService(request2, PROTOCOL, listener2); + int key2 = verifyRequest(NsdManager.REGISTER_SERVICE); + + // First reques fails, second request succeeds + sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key2, request2); + verify(listener2, timeout(mTimeoutMs).times(1)).onServiceRegistered(request2); + + int err = 1; + sendResponse(NsdManager.REGISTER_SERVICE_FAILED, err, key1, request1); + verify(listener1, timeout(mTimeoutMs).times(1)).onRegistrationFailed(request1, err); + + // Client retries first request, it succeeds + manager.registerService(request1, PROTOCOL, listener1); + int key3 = verifyRequest(NsdManager.REGISTER_SERVICE); + + sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key3, request1); + verify(listener1, timeout(mTimeoutMs).times(1)).onServiceRegistered(request1); + + // First request is unregistered, it succeeds + manager.unregisterService(listener1); + int key3again = verifyRequest(NsdManager.UNREGISTER_SERVICE); + assertEquals(key3, key3again); + + sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key3again, null); + verify(listener1, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request1); + + // Second request is unregistered, it fails + manager.unregisterService(listener2); + int key2again = verifyRequest(NsdManager.UNREGISTER_SERVICE); + assertEquals(key2, key2again); + + sendResponse(NsdManager.UNREGISTER_SERVICE_FAILED, err, key2again, null); + verify(listener2, timeout(mTimeoutMs).times(1)).onUnregistrationFailed(request2, err); + + // TODO: do not unregister listener until service is unregistered + // Client retries unregistration of second request, it succeeds + //manager.unregisterService(listener2); + //int key2yetAgain = verifyRequest(NsdManager.UNREGISTER_SERVICE); + //assertEquals(key2, key2yetAgain); + + //sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key2yetAgain, null); + //verify(listener2, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request2); + } + + @Test + public void testDiscoverService() { + NsdManager manager = mManager; + + NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type"); + NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type"); + NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type"); + + NsdManager.DiscoveryListener listener = mock(NsdManager.DiscoveryListener.class); + + // Client registers for discovery, request fails + manager.discoverServices("a_type", PROTOCOL, listener); + int key1 = verifyRequest(NsdManager.DISCOVER_SERVICES); + + int err = 1; + sendResponse(NsdManager.DISCOVER_SERVICES_FAILED, err, key1, null); + verify(listener, timeout(mTimeoutMs).times(1)).onStartDiscoveryFailed("a_type", err); + + // Client retries, request succeeds + manager.discoverServices("a_type", PROTOCOL, listener); + int key2 = verifyRequest(NsdManager.DISCOVER_SERVICES); + + sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key2, reply1); + verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type"); + + + // mdns notifies about services + sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply1); + verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply1); + + sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply2); + verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply2); + + sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply2); + verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply2); + + + // Client unregisters its listener + manager.stopServiceDiscovery(listener); + int key2again = verifyRequest(NsdManager.STOP_DISCOVERY); + assertEquals(key2, key2again); + + // TODO: unregister listener immediately and stop notifying it about services + // Notifications are still passed to the client's listener + sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply1); + verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply1); + + // Client is notified of complete unregistration + sendResponse(NsdManager.STOP_DISCOVERY_SUCCEEDED, 0, key2again, "a_type"); + verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStopped("a_type"); + + // Notifications are not passed to the client anymore + sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply3); + verify(listener, timeout(mTimeoutMs).times(0)).onServiceLost(reply3); + + + // Client registers for service discovery + reset(listener); + manager.discoverServices("a_type", PROTOCOL, listener); + int key3 = verifyRequest(NsdManager.DISCOVER_SERVICES); + + sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key3, reply1); + verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type"); + + // Client unregisters immediately, it fails + manager.stopServiceDiscovery(listener); + int key3again = verifyRequest(NsdManager.STOP_DISCOVERY); + assertEquals(key3, key3again); + + err = 2; + sendResponse(NsdManager.STOP_DISCOVERY_FAILED, err, key3again, "a_type"); + verify(listener, timeout(mTimeoutMs).times(1)).onStopDiscoveryFailed("a_type", err); + + // New notifications are not passed to the client anymore + sendResponse(NsdManager.SERVICE_FOUND, 0, key3, reply1); + verify(listener, timeout(mTimeoutMs).times(0)).onServiceFound(reply1); + } + + @Test + public void testInvalidCalls() { + NsdManager manager = mManager; + + NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class); + NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class); + NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class); + + NsdServiceInfo invalidService = new NsdServiceInfo(null, null); + NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type"); + validService.setPort(2222); + + // Service registration + // - invalid arguments + mustFail(() -> { manager.unregisterService(null); }); + mustFail(() -> { manager.registerService(null, -1, null); }); + mustFail(() -> { manager.registerService(null, PROTOCOL, listener1); }); + mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); }); + mustFail(() -> { manager.registerService(validService, -1, listener1); }); + mustFail(() -> { manager.registerService(validService, PROTOCOL, null); }); + manager.registerService(validService, PROTOCOL, listener1); + // - listener already registered + mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); }); + manager.unregisterService(listener1); + // TODO: make listener immediately reusable + //mustFail(() -> { manager.unregisterService(listener1); }); + //manager.registerService(validService, PROTOCOL, listener1); + + // Discover service + // - invalid arguments + mustFail(() -> { manager.stopServiceDiscovery(null); }); + mustFail(() -> { manager.discoverServices(null, -1, null); }); + mustFail(() -> { manager.discoverServices(null, PROTOCOL, listener2); }); + mustFail(() -> { manager.discoverServices("a_service", -1, listener2); }); + mustFail(() -> { manager.discoverServices("a_service", PROTOCOL, null); }); + manager.discoverServices("a_service", PROTOCOL, listener2); + // - listener already registered + mustFail(() -> { manager.discoverServices("another_service", PROTOCOL, listener2); }); + manager.stopServiceDiscovery(listener2); + // TODO: make listener immediately reusable + //mustFail(() -> { manager.stopServiceDiscovery(listener2); }); + //manager.discoverServices("another_service", PROTOCOL, listener2); + + // Resolver service + // - invalid arguments + mustFail(() -> { manager.resolveService(null, null); }); + mustFail(() -> { manager.resolveService(null, listener3); }); + mustFail(() -> { manager.resolveService(invalidService, listener3); }); + mustFail(() -> { manager.resolveService(validService, null); }); + manager.resolveService(validService, listener3); + // - listener already registered:w + mustFail(() -> { manager.resolveService(validService, listener3); }); + } + + public void mustFail(Runnable fn) { + try { + fn.run(); + fail(); + } catch (Exception expected) { + } + } + + NsdManager makeManager() { + NsdManager manager = new NsdManager(mContext, mService); + // Acknowledge first two messages connecting the AsyncChannel. + verify(mServiceHandler, timeout(mTimeoutMs).times(2)).handleMessage(any()); + reset(mServiceHandler); + assertNotNull(mServiceHandler.chan); + return manager; + } + + int verifyRequest(int expectedMessageType) { + HandlerUtils.waitForIdle(mServiceHandler, mTimeoutMs); + verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any()); + reset(mServiceHandler); + Message received = mServiceHandler.getLastMessage(); + assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what)); + return received.arg2; + } + + void sendResponse(int replyType, int arg, int key, Object obj) { + mServiceHandler.chan.sendMessage(replyType, arg, key, obj); + } + + // Implements the server side of AsyncChannel connection protocol + public static class MockServiceHandler extends Handler { + public final Context context; + public AsyncChannel chan; + public Message lastMessage; + + MockServiceHandler(Looper l, Context c) { + super(l); + context = c; + } + + synchronized Message getLastMessage() { + return lastMessage; + } + + synchronized void setLastMessage(Message msg) { + lastMessage = obtainMessage(); + lastMessage.copyFrom(msg); + } + + @Override + public void handleMessage(Message msg) { + setLastMessage(msg); + if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) { + chan = new AsyncChannel(); + chan.connect(context, this, msg.replyTo); + chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED); + } + } + + void stop() { + getLooper().quitSafely(); + } + + static MockServiceHandler create(Context context) { + HandlerThread t = new HandlerThread("mock-service-handler"); + t.start(); + return new MockServiceHandler(t.getLooper(), context); + } + } +} diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java new file mode 100644 index 0000000000..94dfc7515c --- /dev/null +++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2014 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.nsd; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.StrictMode; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Map; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NsdServiceInfoTest { + + public final static InetAddress LOCALHOST; + static { + // Because test. + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + + InetAddress _host = null; + try { + _host = InetAddress.getLocalHost(); + } catch (UnknownHostException e) { } + LOCALHOST = _host; + } + + @Test + public void testLimits() throws Exception { + NsdServiceInfo info = new NsdServiceInfo(); + + // Non-ASCII keys. + boolean exceptionThrown = false; + try { + info.setAttribute("猫", "meow"); + } catch (IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue(exceptionThrown); + assertEmptyServiceInfo(info); + + // ASCII keys with '=' character. + exceptionThrown = false; + try { + info.setAttribute("kitten=", "meow"); + } catch (IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue(exceptionThrown); + assertEmptyServiceInfo(info); + + // Single key + value length too long. + exceptionThrown = false; + try { + String longValue = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" + + "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" + + "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" + + "ooooooooooooooooooooooooooooong"; // 248 characters. + info.setAttribute("longcat", longValue); // Key + value == 255 characters. + } catch (IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue(exceptionThrown); + assertEmptyServiceInfo(info); + + // Total TXT record length too long. + exceptionThrown = false; + int recordsAdded = 0; + try { + for (int i = 100; i < 300; ++i) { + // 6 char key + 5 char value + 2 bytes overhead = 13 byte record length. + String key = String.format("key%d", i); + info.setAttribute(key, "12345"); + recordsAdded++; + } + } catch (IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue(exceptionThrown); + assertTrue(100 == recordsAdded); + assertTrue(info.getTxtRecord().length == 1300); + } + + @Test + public void testParcel() throws Exception { + NsdServiceInfo emptyInfo = new NsdServiceInfo(); + checkParcelable(emptyInfo); + + NsdServiceInfo fullInfo = new NsdServiceInfo(); + fullInfo.setServiceName("kitten"); + fullInfo.setServiceType("_kitten._tcp"); + fullInfo.setPort(4242); + fullInfo.setHost(LOCALHOST); + checkParcelable(fullInfo); + + NsdServiceInfo noHostInfo = new NsdServiceInfo(); + noHostInfo.setServiceName("kitten"); + noHostInfo.setServiceType("_kitten._tcp"); + noHostInfo.setPort(4242); + checkParcelable(noHostInfo); + + NsdServiceInfo attributedInfo = new NsdServiceInfo(); + attributedInfo.setServiceName("kitten"); + attributedInfo.setServiceType("_kitten._tcp"); + attributedInfo.setPort(4242); + attributedInfo.setHost(LOCALHOST); + attributedInfo.setAttribute("color", "pink"); + attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8")); + attributedInfo.setAttribute("adorable", (String) null); + attributedInfo.setAttribute("sticky", "yes"); + attributedInfo.setAttribute("siblings", new byte[] {}); + attributedInfo.setAttribute("edge cases", new byte[] {0, -1, 127, -128}); + attributedInfo.removeAttribute("sticky"); + checkParcelable(attributedInfo); + + // Sanity check that we actually wrote attributes to attributedInfo. + assertTrue(attributedInfo.getAttributes().keySet().contains("adorable")); + String sound = new String(attributedInfo.getAttributes().get("sound"), "UTF-8"); + assertTrue(sound.equals("にゃあ")); + byte[] edgeCases = attributedInfo.getAttributes().get("edge cases"); + assertTrue(Arrays.equals(edgeCases, new byte[] {0, -1, 127, -128})); + assertFalse(attributedInfo.getAttributes().keySet().contains("sticky")); + } + + public void checkParcelable(NsdServiceInfo original) { + // Write to parcel. + Parcel p = Parcel.obtain(); + Bundle writer = new Bundle(); + writer.putParcelable("test_info", original); + writer.writeToParcel(p, 0); + + // Extract from parcel. + p.setDataPosition(0); + Bundle reader = p.readBundle(); + reader.setClassLoader(NsdServiceInfo.class.getClassLoader()); + NsdServiceInfo result = reader.getParcelable("test_info"); + + // Assert equality of base fields. + assertEquals(original.getServiceName(), result.getServiceName()); + assertEquals(original.getServiceType(), result.getServiceType()); + assertEquals(original.getHost(), result.getHost()); + assertTrue(original.getPort() == result.getPort()); + + // Assert equality of attribute map. + Map originalMap = original.getAttributes(); + Map resultMap = result.getAttributes(); + assertEquals(originalMap.keySet(), resultMap.keySet()); + for (String key : originalMap.keySet()) { + assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key))); + } + } + + public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) { + byte[] txtRecord = shouldBeEmpty.getTxtRecord(); + if (txtRecord == null || txtRecord.length == 0) { + return; + } + fail("NsdServiceInfo.getTxtRecord did not return null but " + Arrays.toString(txtRecord)); + } +} diff --git a/tests/unit/java/android/net/util/DnsUtilsTest.java b/tests/unit/java/android/net/util/DnsUtilsTest.java new file mode 100644 index 0000000000..b626db8d89 --- /dev/null +++ b/tests/unit/java/android/net/util/DnsUtilsTest.java @@ -0,0 +1,216 @@ +/* + * 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 static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_GLOBAL; +import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_LINKLOCAL; +import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_SITELOCAL; + +import static org.junit.Assert.assertEquals; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.InetAddresses; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsUtilsTest { + private InetAddress stringToAddress(@NonNull String addr) { + return InetAddresses.parseNumericAddress(addr); + } + + private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr) { + return makeSortableAddress(addr, null); + } + + private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr, + @Nullable String srcAddr) { + return new DnsUtils.SortableAddress(stringToAddress(addr), + srcAddr != null ? stringToAddress(srcAddr) : null); + } + + @Test + public void testRfc6724Comparator() { + final List test = Arrays.asList( + // Ipv4 + makeSortableAddress("216.58.200.36", "192.168.1.1"), + // global with different scope src + makeSortableAddress("2404:6800:4008:801::2004", "fe80::1111:2222"), + // global without src addr + makeSortableAddress("2404:6800:cafe:801::1"), + // loop back + makeSortableAddress("::1", "::1"), + // link local + makeSortableAddress("fe80::c46f:1cff:fe04:39b4", "fe80::1"), + // teredo tunneling + makeSortableAddress("2001::47c1", "2001::2"), + // 6bone without src addr + makeSortableAddress("3ffe::1234:5678"), + // IPv4-compatible + makeSortableAddress("::216.58.200.36", "::216.58.200.9"), + // 6bone + makeSortableAddress("3ffe::1234:5678", "3ffe::1234:1"), + // IPv4-mapped IPv6 + makeSortableAddress("::ffff:192.168.95.7", "::ffff:192.168.95.1")); + + final List expected = Arrays.asList( + stringToAddress("::1"), // loop back + stringToAddress("fe80::c46f:1cff:fe04:39b4"), // link local + stringToAddress("216.58.200.36"), // Ipv4 + stringToAddress("::ffff:192.168.95.7"), // IPv4-mapped IPv6 + stringToAddress("2001::47c1"), // teredo tunneling + stringToAddress("::216.58.200.36"), // IPv4-compatible + stringToAddress("3ffe::1234:5678"), // 6bone + stringToAddress("2404:6800:4008:801::2004"), // global with different scope src + stringToAddress("2404:6800:cafe:801::1"), // global without src addr + stringToAddress("3ffe::1234:5678")); // 6bone without src addr + + Collections.sort(test, new DnsUtils.Rfc6724Comparator()); + + for (int i = 0; i < test.size(); ++i) { + assertEquals(test.get(i).address, expected.get(i)); + } + + // TODO: add more combinations + } + + @Test + public void testV4SortableAddress() { + // Test V4 address + DnsUtils.SortableAddress test = makeSortableAddress("216.58.200.36"); + assertEquals(test.hasSrcAddr, 0); + assertEquals(test.prefixMatchLen, 0); + assertEquals(test.address, stringToAddress("216.58.200.36")); + assertEquals(test.labelMatch, 0); + assertEquals(test.scopeMatch, 0); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 4); + assertEquals(test.precedence, 35); + + // Test V4 loopback address with the same source address + test = makeSortableAddress("127.1.2.3", "127.1.2.3"); + assertEquals(test.hasSrcAddr, 1); + assertEquals(test.prefixMatchLen, 0); + assertEquals(test.address, stringToAddress("127.1.2.3")); + assertEquals(test.labelMatch, 1); + assertEquals(test.scopeMatch, 1); + assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL); + assertEquals(test.label, 4); + assertEquals(test.precedence, 35); + } + + @Test + public void testV6SortableAddress() { + // Test global address + DnsUtils.SortableAddress test = makeSortableAddress("2404:6800:4008:801::2004"); + assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004")); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + + // Test global address with global source address + test = makeSortableAddress("2404:6800:4008:801::2004", + "2401:fa00:fc:fd00:6d6c:7199:b8e7:41d6"); + assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004")); + assertEquals(test.hasSrcAddr, 1); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.labelMatch, 1); + assertEquals(test.scopeMatch, 1); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + assertEquals(test.prefixMatchLen, 13); + + // Test global address with linklocal source address + test = makeSortableAddress("2404:6800:4008:801::2004", "fe80::c46f:1cff:fe04:39b4"); + assertEquals(test.hasSrcAddr, 1); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.labelMatch, 1); + assertEquals(test.scopeMatch, 0); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + assertEquals(test.prefixMatchLen, 0); + + // Test loopback address with the same source address + test = makeSortableAddress("::1", "::1"); + assertEquals(test.hasSrcAddr, 1); + assertEquals(test.prefixMatchLen, 16 * 8); + assertEquals(test.labelMatch, 1); + assertEquals(test.scopeMatch, 1); + assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL); + assertEquals(test.label, 0); + assertEquals(test.precedence, 50); + + // Test linklocal address + test = makeSortableAddress("fe80::c46f:1cff:fe04:39b4"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + + // Test linklocal address + test = makeSortableAddress("fe80::"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL); + assertEquals(test.label, 1); + assertEquals(test.precedence, 40); + + // Test 6to4 address + test = makeSortableAddress("2002:c000:0204::"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 2); + assertEquals(test.precedence, 30); + + // Test unique local address + test = makeSortableAddress("fc00::c000:13ab"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 13); + assertEquals(test.precedence, 3); + + // Test teredo tunneling address + test = makeSortableAddress("2001::47c1"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 5); + assertEquals(test.precedence, 5); + + // Test IPv4-compatible addresses + test = makeSortableAddress("::216.58.200.36"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 3); + assertEquals(test.precedence, 1); + + // Test site-local address + test = makeSortableAddress("fec0::cafe:3ab2"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_SITELOCAL); + assertEquals(test.label, 11); + assertEquals(test.precedence, 1); + + // Test 6bone address + test = makeSortableAddress("3ffe::1234:5678"); + assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL); + assertEquals(test.label, 12); + assertEquals(test.precedence, 1); + } +} diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt new file mode 100644 index 0000000000..b62bdbcfb5 --- /dev/null +++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt @@ -0,0 +1,145 @@ +/* + * 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.content.Context +import android.content.res.Resources +import android.net.ConnectivityResources +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.MAX_TRANSPORT +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_ETHERNET +import android.net.NetworkCapabilities.TRANSPORT_VPN +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import androidx.test.filters.SmallTest +import com.android.internal.R +import org.junit.After +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock + +/** + * Tests for [KeepaliveUtils]. + * + * Build, install and run with: + * atest android.net.util.KeepaliveUtilsTest + */ +@RunWith(JUnit4::class) +@SmallTest +class KeepaliveUtilsTest { + + // Prepare mocked context with given resource strings. + private fun getMockedContextWithStringArrayRes( + id: Int, + name: String, + res: Array? + ): Context { + val mockRes = mock(Resources::class.java) + doReturn(res).`when`(mockRes).getStringArray(eq(id)) + doReturn(id).`when`(mockRes).getIdentifier(eq(name), any(), any()) + + return mock(Context::class.java).apply { + doReturn(mockRes).`when`(this).getResources() + ConnectivityResources.setResourcesContextForTest(this) + } + } + + @After + fun tearDown() { + ConnectivityResources.setResourcesContextForTest(null) + } + + @Test + fun testGetSupportedKeepalives() { + fun assertRunWithException(res: Array?) { + try { + val mockContext = getMockedContextWithStringArrayRes( + R.array.config_networkSupportedKeepaliveCount, + "config_networkSupportedKeepaliveCount", res) + KeepaliveUtils.getSupportedKeepalives(mockContext) + fail("Expected KeepaliveDeviceConfigurationException") + } catch (expected: KeepaliveUtils.KeepaliveDeviceConfigurationException) { + } + } + + // Check resource with various invalid format. + assertRunWithException(null) + assertRunWithException(arrayOf(null)) + assertRunWithException(arrayOfNulls(10)) + assertRunWithException(arrayOf("")) + assertRunWithException(arrayOf("3,ABC")) + assertRunWithException(arrayOf("6,3,3")) + assertRunWithException(arrayOf("5")) + + // Check resource with invalid slots value. + assertRunWithException(arrayOf("3,-1")) + + // Check resource with invalid transport type. + assertRunWithException(arrayOf("-1,3")) + assertRunWithException(arrayOf("10,3")) + + // Check valid customization generates expected array. + val validRes = arrayOf("0,3", "1,0", "4,4") + val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0) + + val mockContext = getMockedContextWithStringArrayRes( + R.array.config_networkSupportedKeepaliveCount, + "config_networkSupportedKeepaliveCount", validRes) + val actual = KeepaliveUtils.getSupportedKeepalives(mockContext) + assertArrayEquals(expectedValidRes, actual) + } + + @Test + fun testGetSupportedKeepalivesForNetworkCapabilities() { + // Mock customized supported keepalives for each transport type, and assuming: + // 3 for cellular, + // 6 for wifi, + // 0 for others. + val cust = IntArray(MAX_TRANSPORT + 1).apply { + this[TRANSPORT_CELLULAR] = 3 + this[TRANSPORT_WIFI] = 6 + } + + val nc = NetworkCapabilities() + // Check supported keepalives with single transport type. + nc.transportTypes = intArrayOf(TRANSPORT_CELLULAR) + assertEquals(3, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc)) + + // Check supported keepalives with multiple transport types. + nc.transportTypes = intArrayOf(TRANSPORT_WIFI, TRANSPORT_VPN) + assertEquals(0, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc)) + + // Check supported keepalives with non-customized transport type. + nc.transportTypes = intArrayOf(TRANSPORT_ETHERNET) + assertEquals(0, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc)) + + // Check supported keepalives with undefined transport type. + nc.transportTypes = intArrayOf(MAX_TRANSPORT + 1) + try { + KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc) + fail("Expected ArrayIndexOutOfBoundsException") + } catch (expected: ArrayIndexOutOfBoundsException) { + } + } +} diff --git a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt new file mode 100644 index 0000000000..25aa626657 --- /dev/null +++ b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt @@ -0,0 +1,148 @@ +/* + * 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 android.net.util + +import android.content.Context +import android.content.res.Resources +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY +import android.net.ConnectivityResources +import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI +import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE +import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener +import android.provider.Settings +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.test.mock.MockContentResolver +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.connectivity.resources.R +import com.android.internal.util.test.FakeSettingsProvider +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.argThat +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +/** + * Tests for [MultinetworkPolicyTracker]. + * + * Build, install and run with: + * atest android.net.util.MultinetworkPolicyTrackerTest + */ +@RunWith(AndroidJUnit4::class) +@SmallTest +class MultinetworkPolicyTrackerTest { + private val resources = mock(Resources::class.java).also { + doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier( + eq("config_networkAvoidBadWifi"), eq("integer"), any()) + doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi) + } + private val telephonyManager = mock(TelephonyManager::class.java) + private val subscriptionManager = mock(SubscriptionManager::class.java).also { + doReturn(null).`when`(it).getActiveSubscriptionInfo(anyInt()) + } + private val resolver = MockContentResolver().apply { + addProvider(Settings.AUTHORITY, FakeSettingsProvider()) } + private val context = mock(Context::class.java).also { + doReturn(Context.TELEPHONY_SERVICE).`when`(it) + .getSystemServiceName(TelephonyManager::class.java) + doReturn(telephonyManager).`when`(it).getSystemService(Context.TELEPHONY_SERVICE) + doReturn(subscriptionManager).`when`(it) + .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) + doReturn(resolver).`when`(it).contentResolver + doReturn(resources).`when`(it).resources + doReturn(it).`when`(it).createConfigurationContext(any()) + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1") + ConnectivityResources.setResourcesContextForTest(it) + } + private val tracker = MultinetworkPolicyTracker(context, null /* handler */) + + private fun assertMultipathPreference(preference: Int) { + Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE, + preference.toString()) + tracker.updateMeteredMultipathPreference() + assertEquals(preference, tracker.meteredMultipathPreference) + } + + @After + fun tearDown() { + ConnectivityResources.setResourcesContextForTest(null) + } + + @Test + fun testUpdateMeteredMultipathPreference() { + assertMultipathPreference(MULTIPATH_PREFERENCE_HANDOVER) + assertMultipathPreference(MULTIPATH_PREFERENCE_RELIABILITY) + assertMultipathPreference(MULTIPATH_PREFERENCE_PERFORMANCE) + } + + @Test + fun testUpdateAvoidBadWifi() { + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0") + assertTrue(tracker.updateAvoidBadWifi()) + assertFalse(tracker.avoidBadWifi) + + doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi) + assertTrue(tracker.updateAvoidBadWifi()) + assertTrue(tracker.avoidBadWifi) + } + + @Test + fun testOnActiveDataSubscriptionIdChanged() { + val testSubId = 1000 + val subscriptionInfo = SubscriptionInfo(testSubId, ""/* iccId */, 1/* iccId */, + "TMO"/* displayName */, "TMO"/* carrierName */, 1/* nameSource */, 1/* iconTint */, + "123"/* number */, 1/* roaming */, null/* icon */, "310"/* mcc */, "210"/* mnc */, + ""/* countryIso */, false/* isEmbedded */, null/* nativeAccessRules */, + "1"/* cardString */) + doReturn(subscriptionInfo).`when`(subscriptionManager).getActiveSubscriptionInfo(testSubId) + + // Modify avoidBadWifi and meteredMultipathPreference settings value and local variables in + // MultinetworkPolicyTracker should be also updated after subId changed. + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0") + Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE, + MULTIPATH_PREFERENCE_PERFORMANCE.toString()) + + val listenerCaptor = ArgumentCaptor.forClass( + ActiveDataSubscriptionIdListener::class.java) + verify(telephonyManager, times(1)) + .registerTelephonyCallback(any(), listenerCaptor.capture()) + val listener = listenerCaptor.value + listener.onActiveDataSubscriptionIdChanged(testSubId) + + // Check it get resource value with test sub id. + verify(subscriptionManager, times(1)).getActiveSubscriptionInfo(testSubId) + verify(context).createConfigurationContext(argThat { it.mcc == 310 && it.mnc == 210 }) + + // Check if avoidBadWifi and meteredMultipathPreference values have been updated. + assertFalse(tracker.avoidBadWifi) + assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference) + } +} diff --git a/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java b/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java new file mode 100644 index 0000000000..3cfecd5529 --- /dev/null +++ b/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.EPERM; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_STREAM; + +import static junit.framework.Assert.assertEquals; + +import static org.junit.Assert.fail; + +import android.system.ErrnoException; +import android.system.Os; + +import androidx.test.runner.AndroidJUnit4; + +import libcore.io.IoUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@androidx.test.filters.SmallTest +public class NetworkUtilsInternalTest { + + private static void expectSocketSuccess(String msg, int domain, int type) { + try { + IoUtils.closeQuietly(Os.socket(domain, type, 0)); + } catch (ErrnoException e) { + fail(msg + e.getMessage()); + } + } + + private static void expectSocketPemissionError(String msg, int domain, int type) { + try { + IoUtils.closeQuietly(Os.socket(domain, type, 0)); + fail(msg); + } catch (ErrnoException e) { + assertEquals(msg, e.errno, EPERM); + } + } + + private static void expectHasNetworking() { + expectSocketSuccess("Creating a UNIX socket should not have thrown ErrnoException", + AF_UNIX, SOCK_STREAM); + expectSocketSuccess("Creating a AF_INET socket shouldn't have thrown ErrnoException", + AF_INET, SOCK_DGRAM); + expectSocketSuccess("Creating a AF_INET6 socket shouldn't have thrown ErrnoException", + AF_INET6, SOCK_DGRAM); + } + + private static void expectNoNetworking() { + expectSocketSuccess("Creating a UNIX socket should not have thrown ErrnoException", + AF_UNIX, SOCK_STREAM); + expectSocketPemissionError( + "Creating a AF_INET socket should have thrown ErrnoException(EPERM)", + AF_INET, SOCK_DGRAM); + expectSocketPemissionError( + "Creating a AF_INET6 socket should have thrown ErrnoException(EPERM)", + AF_INET6, SOCK_DGRAM); + } + + @Test + public void testSetAllowNetworkingForProcess() { + expectHasNetworking(); + NetworkUtilsInternal.setAllowNetworkingForProcess(false); + expectNoNetworking(); + NetworkUtilsInternal.setAllowNetworkingForProcess(true); + expectHasNetworking(); + } +} diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java new file mode 100644 index 0000000000..46597d19ef --- /dev/null +++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.IpSecAlgorithm; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Unit tests for {@link VpnProfile}. */ +@SmallTest +@RunWith(JUnit4.class) +public class VpnProfileTest { + private static final String DUMMY_PROFILE_KEY = "Test"; + + private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23; + private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24; + + @Test + public void testDefaults() throws Exception { + final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); + + assertEquals(DUMMY_PROFILE_KEY, p.key); + assertEquals("", p.name); + assertEquals(VpnProfile.TYPE_PPTP, p.type); + assertEquals("", p.server); + assertEquals("", p.username); + assertEquals("", p.password); + assertEquals("", p.dnsServers); + assertEquals("", p.searchDomains); + assertEquals("", p.routes); + assertTrue(p.mppe); + assertEquals("", p.l2tpSecret); + assertEquals("", p.ipsecIdentifier); + assertEquals("", p.ipsecSecret); + assertEquals("", p.ipsecUserCert); + assertEquals("", p.ipsecCaCert); + assertEquals("", p.ipsecServerCert); + assertEquals(null, p.proxy); + assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty()); + assertFalse(p.isBypassable); + assertFalse(p.isMetered); + assertEquals(1360, p.maxMtu); + assertFalse(p.areAuthParamsInline); + assertFalse(p.isRestrictedToTestNetworks); + } + + private VpnProfile getSampleIkev2Profile(String key) { + final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */); + + p.name = "foo"; + p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + p.server = "bar"; + p.username = "baz"; + p.password = "qux"; + p.dnsServers = "8.8.8.8"; + p.searchDomains = ""; + p.routes = "0.0.0.0/0"; + p.mppe = false; + p.l2tpSecret = ""; + p.ipsecIdentifier = "quux"; + p.ipsecSecret = "quuz"; + p.ipsecUserCert = "corge"; + p.ipsecCaCert = "grault"; + p.ipsecServerCert = "garply"; + p.proxy = null; + p.setAllowedAlgorithms( + Arrays.asList( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + IpSecAlgorithm.AUTH_HMAC_SHA512, + IpSecAlgorithm.CRYPT_AES_CBC)); + p.isBypassable = true; + p.isMetered = true; + p.maxMtu = 1350; + p.areAuthParamsInline = true; + + // Not saved, but also not compared. + p.saveLogin = true; + + return p; + } + + @Test + public void testEquals() { + assertEquals( + getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY)); + + final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + modified.maxMtu--; + assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified); + } + + @Test + public void testParcelUnparcel() { + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23); + } + + @Test + public void testSetInvalidAlgorithmValueDelimiter() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + + try { + profile.setAllowedAlgorithms( + Arrays.asList("test" + VpnProfile.VALUE_DELIMITER + "test")); + fail("Expected failure due to value separator in algorithm name"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetInvalidAlgorithmListDelimiter() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + + try { + profile.setAllowedAlgorithms( + Arrays.asList("test" + VpnProfile.LIST_DELIMITER + "test")); + fail("Expected failure due to value separator in algorithm name"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testEncodeDecode() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertEquals(profile, decoded); + } + + @Test + public void testEncodeDecodeTooManyValues() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final byte[] tooManyValues = + (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes(); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); + } + + private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) { + // Sort to ensure when we remove, we can do it from greatest first. + Arrays.sort(missingIndices); + + final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode()); + final List parts = + new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER))); + + // Remove from back first to ensure indexing is consistent. + for (int i = missingIndices.length - 1; i >= 0; i--) { + parts.remove(missingIndices[i]); + } + + return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0])); + } + + @Test + public void testEncodeDecodeInvalidNumberOfValues() { + final String tooFewValues = + getEncodedDecodedIkev2ProfileMissingValues( + ENCODED_INDEX_AUTH_PARAMS_INLINE, + ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes())); + } + + @Test + public void testEncodeDecodeMissingIsRestrictedToTestNetworks() { + final String tooFewValues = + getEncodedDecodedIkev2ProfileMissingValues( + ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */); + + // Verify decoding without isRestrictedToTestNetworks defaults to false + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()); + assertFalse(decoded.isRestrictedToTestNetworks); + } + + @Test + public void testEncodeDecodeLoginsNotSaved() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + profile.saveLogin = false; + + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertNotEquals(profile, decoded); + + // Add the username/password back, everything else must be equal. + decoded.username = profile.username; + decoded.password = profile.password; + assertEquals(profile, decoded); + } +} diff --git a/tests/unit/java/com/android/internal/util/BitUtilsTest.java b/tests/unit/java/com/android/internal/util/BitUtilsTest.java new file mode 100644 index 0000000000..d2fbdce977 --- /dev/null +++ b/tests/unit/java/com/android/internal/util/BitUtilsTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import static com.android.internal.util.BitUtils.bytesToBEInt; +import static com.android.internal.util.BitUtils.bytesToLEInt; +import static com.android.internal.util.BitUtils.getUint16; +import static com.android.internal.util.BitUtils.getUint32; +import static com.android.internal.util.BitUtils.getUint8; +import static com.android.internal.util.BitUtils.packBits; +import static com.android.internal.util.BitUtils.uint16; +import static com.android.internal.util.BitUtils.uint32; +import static com.android.internal.util.BitUtils.uint8; +import static com.android.internal.util.BitUtils.unpackBits; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BitUtilsTest { + + @Test + public void testUnsignedByteWideningConversions() { + byte b0 = 0; + byte b1 = 1; + byte bm1 = -1; + assertEquals(0, uint8(b0)); + assertEquals(1, uint8(b1)); + assertEquals(127, uint8(Byte.MAX_VALUE)); + assertEquals(128, uint8(Byte.MIN_VALUE)); + assertEquals(255, uint8(bm1)); + assertEquals(255, uint8((byte)255)); + } + + @Test + public void testUnsignedShortWideningConversions() { + short s0 = 0; + short s1 = 1; + short sm1 = -1; + assertEquals(0, uint16(s0)); + assertEquals(1, uint16(s1)); + assertEquals(32767, uint16(Short.MAX_VALUE)); + assertEquals(32768, uint16(Short.MIN_VALUE)); + assertEquals(65535, uint16(sm1)); + assertEquals(65535, uint16((short)65535)); + } + + @Test + public void testUnsignedShortComposition() { + byte b0 = 0; + byte b1 = 1; + byte b2 = 2; + byte b10 = 10; + byte b16 = 16; + byte b128 = -128; + byte b224 = -32; + byte b255 = -1; + assertEquals(0x0000, uint16(b0, b0)); + assertEquals(0xffff, uint16(b255, b255)); + assertEquals(0x0a01, uint16(b10, b1)); + assertEquals(0x8002, uint16(b128, b2)); + assertEquals(0x01ff, uint16(b1, b255)); + assertEquals(0x80ff, uint16(b128, b255)); + assertEquals(0xe010, uint16(b224, b16)); + } + + @Test + public void testUnsignedIntWideningConversions() { + assertEquals(0, uint32(0)); + assertEquals(1, uint32(1)); + assertEquals(2147483647L, uint32(Integer.MAX_VALUE)); + assertEquals(2147483648L, uint32(Integer.MIN_VALUE)); + assertEquals(4294967295L, uint32(-1)); + assertEquals(4294967295L, uint32((int)4294967295L)); + } + + @Test + public void testBytesToInt() { + assertEquals(0x00000000, bytesToBEInt(bytes(0, 0, 0, 0))); + assertEquals(0xffffffff, bytesToBEInt(bytes(255, 255, 255, 255))); + assertEquals(0x0a000001, bytesToBEInt(bytes(10, 0, 0, 1))); + assertEquals(0x0a000002, bytesToBEInt(bytes(10, 0, 0, 2))); + assertEquals(0x0a001fff, bytesToBEInt(bytes(10, 0, 31, 255))); + assertEquals(0xe0000001, bytesToBEInt(bytes(224, 0, 0, 1))); + + assertEquals(0x00000000, bytesToLEInt(bytes(0, 0, 0, 0))); + assertEquals(0x01020304, bytesToLEInt(bytes(4, 3, 2, 1))); + assertEquals(0xffff0000, bytesToLEInt(bytes(0, 0, 255, 255))); + } + + @Test + public void testUnsignedGetters() { + ByteBuffer b = ByteBuffer.allocate(4); + b.putInt(0xffff); + + assertEquals(0x0, getUint8(b, 0)); + assertEquals(0x0, getUint8(b, 1)); + assertEquals(0xff, getUint8(b, 2)); + assertEquals(0xff, getUint8(b, 3)); + + assertEquals(0x0, getUint16(b, 0)); + assertEquals(0xffff, getUint16(b, 2)); + + b.rewind(); + b.putInt(0xffffffff); + assertEquals(0xffffffffL, getUint32(b, 0)); + } + + @Test + public void testBitsPacking() { + BitPackingTestCase[] testCases = { + new BitPackingTestCase(0, ints()), + new BitPackingTestCase(1, ints(0)), + new BitPackingTestCase(2, ints(1)), + new BitPackingTestCase(3, ints(0, 1)), + new BitPackingTestCase(4, ints(2)), + new BitPackingTestCase(6, ints(1, 2)), + new BitPackingTestCase(9, ints(0, 3)), + new BitPackingTestCase(~Long.MAX_VALUE, ints(63)), + new BitPackingTestCase(~Long.MAX_VALUE + 1, ints(0, 63)), + new BitPackingTestCase(~Long.MAX_VALUE + 2, ints(1, 63)), + }; + for (BitPackingTestCase tc : testCases) { + int[] got = unpackBits(tc.packedBits); + assertTrue( + "unpackBits(" + + tc.packedBits + + "): expected " + + Arrays.toString(tc.bits) + + " but got " + + Arrays.toString(got), + Arrays.equals(tc.bits, got)); + } + for (BitPackingTestCase tc : testCases) { + long got = packBits(tc.bits); + assertEquals( + "packBits(" + + Arrays.toString(tc.bits) + + "): expected " + + tc.packedBits + + " but got " + + got, + tc.packedBits, + got); + } + + long[] moreTestCases = { + 0, 1, -1, 23895, -908235, Long.MAX_VALUE, Long.MIN_VALUE, new Random().nextLong(), + }; + for (long l : moreTestCases) { + assertEquals(l, packBits(unpackBits(l))); + } + } + + static byte[] bytes(int b1, int b2, int b3, int b4) { + return new byte[] {b(b1), b(b2), b(b3), b(b4)}; + } + + static byte b(int i) { + return (byte) i; + } + + static int[] ints(int... array) { + return array; + } + + static class BitPackingTestCase { + final int[] bits; + final long packedBits; + + BitPackingTestCase(long packedBits, int[] bits) { + this.bits = bits; + this.packedBits = packedBits; + } + } +} diff --git a/tests/unit/java/com/android/internal/util/RingBufferTest.java b/tests/unit/java/com/android/internal/util/RingBufferTest.java new file mode 100644 index 0000000000..d06095a690 --- /dev/null +++ b/tests/unit/java/com/android/internal/util/RingBufferTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RingBufferTest { + + @Test + public void testEmptyRingBuffer() { + RingBuffer buffer = new RingBuffer<>(String.class, 100); + + assertArrayEquals(new String[0], buffer.toArray()); + } + + @Test + public void testIncorrectConstructorArguments() { + try { + RingBuffer buffer = new RingBuffer<>(String.class, -10); + fail("Should not be able to create a negative capacity RingBuffer"); + } catch (IllegalArgumentException expected) { + } + + try { + RingBuffer buffer = new RingBuffer<>(String.class, 0); + fail("Should not be able to create a 0 capacity RingBuffer"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testRingBufferWithNoWrapping() { + RingBuffer buffer = new RingBuffer<>(String.class, 100); + + buffer.append("a"); + buffer.append("b"); + buffer.append("c"); + buffer.append("d"); + buffer.append("e"); + + String[] expected = {"a", "b", "c", "d", "e"}; + assertArrayEquals(expected, buffer.toArray()); + } + + @Test + public void testRingBufferWithCapacity1() { + RingBuffer buffer = new RingBuffer<>(String.class, 1); + + buffer.append("a"); + assertArrayEquals(new String[]{"a"}, buffer.toArray()); + + buffer.append("b"); + assertArrayEquals(new String[]{"b"}, buffer.toArray()); + + buffer.append("c"); + assertArrayEquals(new String[]{"c"}, buffer.toArray()); + + buffer.append("d"); + assertArrayEquals(new String[]{"d"}, buffer.toArray()); + + buffer.append("e"); + assertArrayEquals(new String[]{"e"}, buffer.toArray()); + } + + @Test + public void testRingBufferWithWrapping() { + int capacity = 100; + RingBuffer buffer = new RingBuffer<>(String.class, capacity); + + buffer.append("a"); + buffer.append("b"); + buffer.append("c"); + buffer.append("d"); + buffer.append("e"); + + String[] expected1 = {"a", "b", "c", "d", "e"}; + assertArrayEquals(expected1, buffer.toArray()); + + String[] expected2 = new String[capacity]; + int firstIndex = 0; + int lastIndex = capacity - 1; + + expected2[firstIndex] = "e"; + for (int i = 1; i < capacity; i++) { + buffer.append("x"); + expected2[i] = "x"; + } + assertArrayEquals(expected2, buffer.toArray()); + + buffer.append("x"); + expected2[firstIndex] = "x"; + assertArrayEquals(expected2, buffer.toArray()); + + for (int i = 0; i < 10; i++) { + for (String s : expected2) { + buffer.append(s); + } + } + assertArrayEquals(expected2, buffer.toArray()); + + buffer.append("a"); + expected2[lastIndex] = "a"; + assertArrayEquals(expected2, buffer.toArray()); + } + + @Test + public void testGetNextSlot() { + int capacity = 100; + RingBuffer buffer = new RingBuffer<>(DummyClass1.class, capacity); + + final DummyClass1[] actual = new DummyClass1[capacity]; + final DummyClass1[] expected = new DummyClass1[capacity]; + for (int i = 0; i < capacity; ++i) { + final DummyClass1 obj = buffer.getNextSlot(); + obj.x = capacity * i; + actual[i] = obj; + expected[i] = new DummyClass1(); + expected[i].x = capacity * i; + } + assertArrayEquals(expected, buffer.toArray()); + + for (int i = 0; i < capacity; ++i) { + if (actual[i] != buffer.getNextSlot()) { + fail("getNextSlot() should re-use objects if available"); + } + } + + RingBuffer buffer2 = new RingBuffer<>(DummyClass2.class, capacity); + assertNull("getNextSlot() should return null if the object can't be initiated " + + "(No nullary constructor)", buffer2.getNextSlot()); + + RingBuffer buffer3 = new RingBuffer<>(DummyClass3.class, capacity); + assertNull("getNextSlot() should return null if the object can't be initiated " + + "(Inaccessible class)", buffer3.getNextSlot()); + } + + public static final class DummyClass1 { + int x; + + public boolean equals(Object o) { + if (o instanceof DummyClass1) { + final DummyClass1 other = (DummyClass1) o; + return other.x == this.x; + } + return false; + } + } + + public static final class DummyClass2 { + public DummyClass2(int x) {} + } + + private static final class DummyClass3 {} +} diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java new file mode 100644 index 0000000000..ab50798206 --- /dev/null +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -0,0 +1,12461 @@ +/* + * 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 com.android.server; + +import static android.Manifest.permission.CHANGE_NETWORK_STATE; +import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; +import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.NETWORK_FACTORY; +import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.ACTION_PACKAGE_REPLACED; +import static android.content.Intent.ACTION_USER_ADDED; +import static android.content.Intent.ACTION_USER_REMOVED; +import static android.content.Intent.ACTION_USER_UNLOCKED; +import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; +import static android.content.pm.PackageManager.FEATURE_WIFI; +import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT; +import static android.content.pm.PackageManager.GET_PERMISSIONS; +import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; +import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER; +import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK; +import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED; +import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER; +import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; +import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; +import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO; +import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; +import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; +import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; +import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; +import static android.net.ConnectivityManager.TYPE_PROXY; +import static android.net.ConnectivityManager.TYPE_VPN; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; +import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_BIP; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; +import static android.net.NetworkCapabilities.NET_CAPABILITY_IA; +import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; +import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM; +import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; +import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP; +import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION; +import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS; +import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; +import static android.net.NetworkCapabilities.REDACT_NONE; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; +import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; +import static android.os.Process.INVALID_UID; +import static android.system.OsConstants.IPPROTO_TCP; + +import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType; +import static com.android.testutils.ConcurrentUtils.await; +import static com.android.testutils.ConcurrentUtils.durationOf; +import static com.android.testutils.ExceptionUtils.ignoreExceptions; +import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor; +import static com.android.testutils.MiscAsserts.assertContainsAll; +import static com.android.testutils.MiscAsserts.assertContainsExactly; +import static com.android.testutils.MiscAsserts.assertEmpty; +import static com.android.testutils.MiscAsserts.assertLength; +import static com.android.testutils.MiscAsserts.assertRunsInAtMost; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +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.AdditionalMatchers.aryEq; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.AppOpsManager; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.usage.NetworkStatsManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.location.LocationManager; +import android.net.CaptivePortalData; +import android.net.ConnectionInfo; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.ConnectivityManager.PacketKeepalive; +import android.net.ConnectivityManager.PacketKeepaliveCallback; +import android.net.ConnectivityManager.TooManyRequestsException; +import android.net.ConnectivityResources; +import android.net.ConnectivitySettingsManager; +import android.net.ConnectivityThread; +import android.net.DataStallReportParcelable; +import android.net.EthernetManager; +import android.net.IConnectivityDiagnosticsCallback; +import android.net.IDnsResolver; +import android.net.INetd; +import android.net.INetworkMonitor; +import android.net.INetworkMonitorCallbacks; +import android.net.IOnCompleteListener; +import android.net.IQosCallback; +import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; +import android.net.IpPrefix; +import android.net.IpSecManager; +import android.net.IpSecManager.UdpEncapsulationSocket; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.MatchAllNetworkSpecifier; +import android.net.NativeNetworkConfig; +import android.net.NativeNetworkType; +import android.net.Network; +import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkFactory; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkPolicyManager; +import android.net.NetworkPolicyManager.NetworkPolicyCallback; +import android.net.NetworkRequest; +import android.net.NetworkScore; +import android.net.NetworkSpecifier; +import android.net.NetworkStack; +import android.net.NetworkStackClient; +import android.net.NetworkStateSnapshot; +import android.net.NetworkTestResultParcelable; +import android.net.OemNetworkPreferences; +import android.net.ProxyInfo; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSession; +import android.net.ResolverParamsParcel; +import android.net.RouteInfo; +import android.net.RouteInfoParcel; +import android.net.SocketKeepalive; +import android.net.TransportInfo; +import android.net.UidRange; +import android.net.UidRangeParcel; +import android.net.UnderlyingNetworkInfo; +import android.net.Uri; +import android.net.VpnManager; +import android.net.VpnTransportInfo; +import android.net.metrics.IpConnectivityLog; +import android.net.resolv.aidl.Nat64PrefixEventParcel; +import android.net.resolv.aidl.PrivateDnsValidationEventParcel; +import android.net.shared.NetworkMonitorUtils; +import android.net.shared.PrivateDnsConfig; +import android.net.util.MultinetworkPolicyTracker; +import android.os.BadParcelableException; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.SystemClock; +import android.os.SystemConfigManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.security.Credentials; +import android.system.Os; +import android.telephony.TelephonyManager; +import android.telephony.data.EpsBearerQosSessionAttributes; +import android.telephony.data.NrQosSessionAttributes; +import android.test.mock.MockContentResolver; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import android.util.Pair; +import android.util.Range; +import android.util.SparseArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.connectivity.resources.R; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.WakeupMessage; +import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.net.module.util.ArrayTrackRecord; +import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo; +import com.android.server.connectivity.ConnectivityConstants; +import com.android.server.connectivity.MockableSystemProperties; +import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.NetworkAgentInfo; +import com.android.server.connectivity.NetworkNotificationManager.NotificationType; +import com.android.server.connectivity.ProxyTracker; +import com.android.server.connectivity.QosCallbackTracker; +import com.android.server.connectivity.Vpn; +import com.android.server.connectivity.VpnProfileStore; +import com.android.server.net.NetworkPinner; +import com.android.testutils.ExceptionUtils; +import com.android.testutils.HandlerUtils; +import com.android.testutils.RecorderCallback.CallbackEntry; +import com.android.testutils.TestableNetworkCallback; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.stubbing.Answer; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.DatagramSocket; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import kotlin.reflect.KClass; + +/** + * Tests for {@link ConnectivityService}. + * + * Build, install and run with: + * runtest frameworks-net -c com.android.server.ConnectivityServiceTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConnectivityServiceTest { + private static final String TAG = "ConnectivityServiceTest"; + + private static final int TIMEOUT_MS = 500; + // Broadcasts can take a long time to be delivered. The test will not wait for that long unless + // there is a failure, so use a long timeout. + private static final int BROADCAST_TIMEOUT_MS = 30_000; + private static final int TEST_LINGER_DELAY_MS = 400; + private static final int TEST_NASCENT_DELAY_MS = 300; + // Chosen to be less than the linger and nascent timeout. This ensures that we can distinguish + // between a LOST callback that arrives immediately and a LOST callback that arrives after + // the linger/nascent timeout. For this, our assertions should run fast enough to leave + // less than (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are + // supposedly fired, and the time we call expectCallback. + private static final int TEST_CALLBACK_TIMEOUT_MS = 250; + // Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to + // complete before callbacks are verified. + private static final int TEST_REQUEST_TIMEOUT_MS = 150; + + private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000; + + private static final long TIMESTAMP = 1234L; + + private static final int NET_ID = 110; + private static final int OEM_PREF_ANY_NET_ID = -1; + // Set a non-zero value to verify the flow to set tcp init rwnd value. + private static final int TEST_TCP_INIT_RWND = 60; + + // Used for testing the per-work-profile default network. + private static final int TEST_APP_ID = 103; + private static final int TEST_WORK_PROFILE_USER_ID = 2; + private static final int TEST_WORK_PROFILE_APP_UID = + UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID); + private static final String CLAT_PREFIX = "v4-"; + private static final String MOBILE_IFNAME = "test_rmnet_data0"; + private static final String WIFI_IFNAME = "test_wlan0"; + private static final String WIFI_WOL_IFNAME = "test_wlan_wol"; + private static final String VPN_IFNAME = "tun10042"; + private static final String TEST_PACKAGE_NAME = "com.android.test.package"; + private static final int TEST_PACKAGE_UID = 123; + private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn"; + + private static final String INTERFACE_NAME = "interface"; + + private static final String TEST_VENUE_URL_NA_PASSPOINT = "https://android.com/"; + private static final String TEST_VENUE_URL_NA_OTHER = "https://example.com/"; + private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT = + "https://android.com/terms/"; + private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER = + "https://example.com/terms/"; + private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/"; + private static final String TEST_USER_PORTAL_API_URL_CAPPORT = + "https://android.com/user/api/capport/"; + private static final String TEST_FRIENDLY_NAME = "Network friendly name"; + private static final String TEST_REDIRECT_URL = "http://example.com/firstPath"; + + private MockContext mServiceContext; + private HandlerThread mCsHandlerThread; + private HandlerThread mVMSHandlerThread; + private ConnectivityService.Dependencies mDeps; + private ConnectivityService mService; + private WrappedConnectivityManager mCm; + private TestNetworkAgentWrapper mWiFiNetworkAgent; + private TestNetworkAgentWrapper mCellNetworkAgent; + private TestNetworkAgentWrapper mEthernetNetworkAgent; + private MockVpn mMockVpn; + private Context mContext; + private NetworkPolicyCallback mPolicyCallback; + private WrappedMultinetworkPolicyTracker mPolicyTracker; + private HandlerThread mAlarmManagerThread; + private TestNetIdManager mNetIdManager; + private QosCallbackMockHelper mQosCallbackMockHelper; + private QosCallbackTracker mQosCallbackTracker; + private VpnManagerService mVpnManagerService; + private TestNetworkCallback mDefaultNetworkCallback; + private TestNetworkCallback mSystemDefaultNetworkCallback; + private TestNetworkCallback mProfileDefaultNetworkCallback; + + // State variables required to emulate NetworkPolicyManagerService behaviour. + private int mBlockedReasons = BLOCKED_REASON_NONE; + + @Mock DeviceIdleInternal mDeviceIdleInternal; + @Mock INetworkManagementService mNetworkManagementService; + @Mock NetworkStatsManager mStatsManager; + @Mock IDnsResolver mMockDnsResolver; + @Mock INetd mMockNetd; + @Mock NetworkStackClient mNetworkStack; + @Mock PackageManager mPackageManager; + @Mock UserManager mUserManager; + @Mock NotificationManager mNotificationManager; + @Mock AlarmManager mAlarmManager; + @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback; + @Mock IBinder mIBinder; + @Mock LocationManager mLocationManager; + @Mock AppOpsManager mAppOpsManager; + @Mock TelephonyManager mTelephonyManager; + @Mock MockableSystemProperties mSystemProperties; + @Mock EthernetManager mEthernetManager; + @Mock NetworkPolicyManager mNetworkPolicyManager; + @Mock VpnProfileStore mVpnProfileStore; + @Mock SystemConfigManager mSystemConfigManager; + @Mock Resources mResources; + + private ArgumentCaptor mResolverParamsParcelCaptor = + ArgumentCaptor.forClass(ResolverParamsParcel.class); + + // This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods + // do not go through ConnectivityService but talk to netd directly, so they don't automatically + // reflect the state of our test ConnectivityService. + private class WrappedConnectivityManager extends ConnectivityManager { + private Network mFakeBoundNetwork; + + public synchronized boolean bindProcessToNetwork(Network network) { + mFakeBoundNetwork = network; + return true; + } + + public synchronized Network getBoundNetworkForProcess() { + return mFakeBoundNetwork; + } + + public WrappedConnectivityManager(Context context, ConnectivityService service) { + super(context, service); + } + } + + private class MockContext extends BroadcastInterceptingContext { + private final MockContentResolver mContentResolver; + + @Spy private Resources mInternalResources; + private final LinkedBlockingQueue mStartedActivities = new LinkedBlockingQueue<>(); + + // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant + private final HashMap mMockedPermissions = new HashMap<>(); + + MockContext(Context base, ContentProvider settingsProvider) { + super(base); + + mInternalResources = spy(base.getResources()); + when(mInternalResources.getStringArray(com.android.internal.R.array.networkAttributes)) + .thenReturn(new String[] { + "wifi,1,1,1,-1,true", + "mobile,0,0,0,-1,true", + "mobile_mms,2,0,2,60000,true", + "mobile_supl,3,0,2,60000,true", + }); + + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, settingsProvider); + } + + @Override + public void startActivityAsUser(Intent intent, UserHandle handle) { + mStartedActivities.offer(intent); + } + + public Intent expectStartActivityIntent(int timeoutMs) { + Intent intent = null; + try { + intent = mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) {} + assertNotNull("Did not receive sign-in intent after " + timeoutMs + "ms", intent); + return intent; + } + + public void expectNoStartActivityIntent(int timeoutMs) { + try { + assertNull("Received unexpected Intent to start activity", + mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) {} + } + + @Override + public ComponentName startService(Intent service) { + final String action = service.getAction(); + if (!VpnConfig.SERVICE_INTERFACE.equals(action)) { + fail("Attempt to start unknown service, action=" + action); + } + return new ComponentName(service.getPackage(), "com.android.test.Service"); + } + + @Override + public Object getSystemService(String name) { + if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; + if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager; + if (Context.USER_SERVICE.equals(name)) return mUserManager; + if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager; + if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager; + if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager; + if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; + if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager; + if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager; + if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager; + if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager; + return super.getSystemService(name); + } + + final HashMap mUserManagers = new HashMap<>(); + @Override + public Context createContextAsUser(UserHandle user, int flags) { + final Context asUser = mock(Context.class, AdditionalAnswers.delegatesTo(this)); + doReturn(user).when(asUser).getUser(); + doAnswer((inv) -> { + final UserManager um = mUserManagers.computeIfAbsent(user, + u -> mock(UserManager.class, AdditionalAnswers.delegatesTo(mUserManager))); + return um; + }).when(asUser).getSystemService(Context.USER_SERVICE); + return asUser; + } + + public void setWorkProfile(@NonNull final UserHandle userHandle, boolean value) { + // This relies on all contexts for a given user returning the same UM mock + final UserManager umMock = createContextAsUser(userHandle, 0 /* flags */) + .getSystemService(UserManager.class); + doReturn(value).when(umMock).isManagedProfile(); + doReturn(value).when(mUserManager).isManagedProfile(eq(userHandle.getIdentifier())); + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + + @Override + public Resources getResources() { + return mInternalResources; + } + + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + private int checkMockedPermission(String permission, Supplier ifAbsent) { + final Integer granted = mMockedPermissions.get(permission); + return granted != null ? granted : ifAbsent.get(); + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + return checkMockedPermission( + permission, () -> super.checkPermission(permission, pid, uid)); + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + return checkMockedPermission( + permission, () -> super.checkCallingOrSelfPermission(permission)); + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + final Integer granted = mMockedPermissions.get(permission); + if (granted == null) { + super.enforceCallingOrSelfPermission(permission, message); + return; + } + + if (!granted.equals(PERMISSION_GRANTED)) { + throw new SecurityException("[Test] permission denied: " + permission); + } + } + + /** + * Mock checks for the specified permission, and have them behave as per {@code granted}. + * + *

      Passing null reverts to default behavior, which does a real permission check on the + * test package. + * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or + * {@link PackageManager#PERMISSION_DENIED}. + */ + public void setPermission(String permission, Integer granted) { + mMockedPermissions.put(permission, granted); + } + } + + private void waitForIdle() { + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + waitForIdle(mCellNetworkAgent, TIMEOUT_MS); + waitForIdle(mWiFiNetworkAgent, TIMEOUT_MS); + waitForIdle(mEthernetNetworkAgent, TIMEOUT_MS); + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS); + } + + private void waitForIdle(TestNetworkAgentWrapper agent, long timeoutMs) { + if (agent == null) { + return; + } + agent.waitForIdle(timeoutMs); + } + + @Test + public void testWaitForIdle() throws Exception { + final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. + + // Tests that waitForIdle returns immediately if the service is already idle. + for (int i = 0; i < attempts; i++) { + waitForIdle(); + } + + // Bring up a network that we can use to send messages to ConnectivityService. + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + b.expectBroadcast(); + Network n = mWiFiNetworkAgent.getNetwork(); + assertNotNull(n); + + // Tests that calling waitForIdle waits for messages to be processed. + for (int i = 0; i < attempts; i++) { + mWiFiNetworkAgent.setSignalStrength(i); + waitForIdle(); + assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength()); + } + } + + // This test has an inherent race condition in it, and cannot be enabled for continuous testing + // or presubmit tests. It is kept for manual runs and documentation purposes. + @Ignore + public void verifyThatNotWaitingForIdleCausesRaceConditions() throws Exception { + // Bring up a network that we can use to send messages to ConnectivityService. + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + b.expectBroadcast(); + Network n = mWiFiNetworkAgent.getNetwork(); + assertNotNull(n); + + // Ensure that not calling waitForIdle causes a race condition. + final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. + for (int i = 0; i < attempts; i++) { + mWiFiNetworkAgent.setSignalStrength(i); + if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) { + // We hit a race condition, as expected. Pass the test. + return; + } + } + + // No race? There is a bug in this test. + fail("expected race condition at least once in " + attempts + " attempts"); + } + + private class TestNetworkAgentWrapper extends NetworkAgentWrapper { + private static final int VALIDATION_RESULT_INVALID = 0; + + private static final long DATA_STALL_TIMESTAMP = 10L; + private static final int DATA_STALL_DETECTION_METHOD = 1; + + private INetworkMonitor mNetworkMonitor; + private INetworkMonitorCallbacks mNmCallbacks; + private int mNmValidationResult = VALIDATION_RESULT_INVALID; + private int mProbesCompleted; + private int mProbesSucceeded; + private String mNmValidationRedirectUrl = null; + private boolean mNmProvNotificationRequested = false; + private Runnable mCreatedCallback; + private Runnable mUnwantedCallback; + private Runnable mDisconnectedCallback; + + private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); + // Contains the redirectUrl from networkStatus(). Before reading, wait for + // mNetworkStatusReceived. + private String mRedirectUrl; + + TestNetworkAgentWrapper(int transport) throws Exception { + this(transport, new LinkProperties(), null); + } + + TestNetworkAgentWrapper(int transport, LinkProperties linkProperties) + throws Exception { + this(transport, linkProperties, null); + } + + private TestNetworkAgentWrapper(int transport, LinkProperties linkProperties, + NetworkCapabilities ncTemplate) throws Exception { + super(transport, linkProperties, ncTemplate, mServiceContext); + + // Waits for the NetworkAgent to be registered, which includes the creation of the + // NetworkMonitor. + waitForIdle(TIMEOUT_MS); + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS); + } + + @Override + protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties, + NetworkAgentConfig nac) throws Exception { + mNetworkMonitor = mock(INetworkMonitor.class); + + final Answer validateAnswer = inv -> { + new Thread(ignoreExceptions(this::onValidationRequested)).start(); + return null; + }; + + doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any()); + doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt()); + + final ArgumentCaptor nmNetworkCaptor = ArgumentCaptor.forClass(Network.class); + final ArgumentCaptor nmCbCaptor = + ArgumentCaptor.forClass(INetworkMonitorCallbacks.class); + doNothing().when(mNetworkStack).makeNetworkMonitor( + nmNetworkCaptor.capture(), + any() /* name */, + nmCbCaptor.capture()); + + final InstrumentedNetworkAgent na = + new InstrumentedNetworkAgent(this, linkProperties, nac) { + @Override + public void networkStatus(int status, String redirectUrl) { + mRedirectUrl = redirectUrl; + mNetworkStatusReceived.open(); + } + + @Override + public void onNetworkCreated() { + super.onNetworkCreated(); + if (mCreatedCallback != null) mCreatedCallback.run(); + } + + @Override + public void onNetworkUnwanted() { + super.onNetworkUnwanted(); + if (mUnwantedCallback != null) mUnwantedCallback.run(); + } + + @Override + public void onNetworkDestroyed() { + super.onNetworkDestroyed(); + if (mDisconnectedCallback != null) mDisconnectedCallback.run(); + } + }; + + assertEquals(na.getNetwork().netId, nmNetworkCaptor.getValue().netId); + mNmCallbacks = nmCbCaptor.getValue(); + + mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor); + + return na; + } + + private void onValidationRequested() throws Exception { + if (mNmProvNotificationRequested + && ((mNmValidationResult & NETWORK_VALIDATION_RESULT_VALID) != 0)) { + mNmCallbacks.hideProvisioningNotification(); + mNmProvNotificationRequested = false; + } + + mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded); + final NetworkTestResultParcelable p = new NetworkTestResultParcelable(); + p.result = mNmValidationResult; + p.probesAttempted = mProbesCompleted; + p.probesSucceeded = mProbesSucceeded; + p.redirectUrl = mNmValidationRedirectUrl; + p.timestampMillis = TIMESTAMP; + mNmCallbacks.notifyNetworkTestedWithExtras(p); + + if (mNmValidationRedirectUrl != null) { + mNmCallbacks.showProvisioningNotification( + "test_provisioning_notif_action", TEST_PACKAGE_NAME); + mNmProvNotificationRequested = true; + } + } + + /** + * Connect without adding any internet capability. + */ + public void connectWithoutInternet() { + super.connect(); + } + + /** + * Transition this NetworkAgent to CONNECTED state with NET_CAPABILITY_INTERNET. + * @param validated Indicate if network should pretend to be validated. + */ + public void connect(boolean validated) { + connect(validated, true, false /* isStrictMode */); + } + + /** + * Transition this NetworkAgent to CONNECTED state. + * @param validated Indicate if network should pretend to be validated. + * @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET. + */ + public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) { + assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET)); + + ConnectivityManager.NetworkCallback callback = null; + final ConditionVariable validatedCv = new ConditionVariable(); + if (validated) { + setNetworkValid(isStrictMode); + NetworkRequest request = new NetworkRequest.Builder() + .addTransportType(getNetworkCapabilities().getTransportTypes()[0]) + .clearCapabilities() + .build(); + callback = new ConnectivityManager.NetworkCallback() { + public void onCapabilitiesChanged(Network network, + NetworkCapabilities networkCapabilities) { + if (network.equals(getNetwork()) && + networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { + validatedCv.open(); + } + } + }; + mCm.registerNetworkCallback(request, callback); + } + if (hasInternet) { + addCapability(NET_CAPABILITY_INTERNET); + } + + connectWithoutInternet(); + + if (validated) { + // Wait for network to validate. + waitFor(validatedCv); + setNetworkInvalid(isStrictMode); + } + + if (callback != null) mCm.unregisterNetworkCallback(callback); + } + + public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) { + setNetworkPortal(redirectUrl, isStrictMode); + connect(false, true /* hasInternet */, isStrictMode); + } + + public void connectWithPartialConnectivity() { + setNetworkPartial(); + connect(false); + } + + public void connectWithPartialValidConnectivity(boolean isStrictMode) { + setNetworkPartialValid(isStrictMode); + connect(false, true /* hasInternet */, isStrictMode); + } + + void setNetworkValid(boolean isStrictMode) { + mNmValidationResult = NETWORK_VALIDATION_RESULT_VALID; + mNmValidationRedirectUrl = null; + int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS; + if (isStrictMode) { + probesSucceeded |= NETWORK_VALIDATION_PROBE_PRIVDNS; + } + // The probesCompleted equals to probesSucceeded for the case of valid network, so put + // the same value into two different parameter of the method. + setProbesStatus(probesSucceeded, probesSucceeded); + } + + void setNetworkInvalid(boolean isStrictMode) { + mNmValidationResult = VALIDATION_RESULT_INVALID; + mNmValidationRedirectUrl = null; + int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS + | NETWORK_VALIDATION_PROBE_HTTP; + int probesSucceeded = 0; + // If the isStrictMode is true, it means the network is invalid when NetworkMonitor + // tried to validate the private DNS but failed. + if (isStrictMode) { + probesCompleted &= ~NETWORK_VALIDATION_PROBE_HTTP; + probesSucceeded = probesCompleted; + probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS; + } + setProbesStatus(probesCompleted, probesSucceeded); + } + + void setNetworkPortal(String redirectUrl, boolean isStrictMode) { + setNetworkInvalid(isStrictMode); + mNmValidationRedirectUrl = redirectUrl; + // Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP + // in the beginning, so the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet. + int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP; + int probesSucceeded = VALIDATION_RESULT_INVALID; + if (isStrictMode) { + probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS; + } + setProbesStatus(probesCompleted, probesSucceeded); + } + + void setNetworkPartial() { + mNmValidationResult = NETWORK_VALIDATION_RESULT_PARTIAL; + mNmValidationRedirectUrl = null; + int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS + | NETWORK_VALIDATION_PROBE_FALLBACK; + int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK; + setProbesStatus(probesCompleted, probesSucceeded); + } + + void setNetworkPartialValid(boolean isStrictMode) { + setNetworkPartial(); + mNmValidationResult |= NETWORK_VALIDATION_RESULT_VALID; + int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS + | NETWORK_VALIDATION_PROBE_HTTP; + int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP; + // Suppose the partial network cannot pass the private DNS validation as well, so only + // add NETWORK_VALIDATION_PROBE_DNS in probesCompleted but not probesSucceeded. + if (isStrictMode) { + probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS; + } + setProbesStatus(probesCompleted, probesSucceeded); + } + + void setProbesStatus(int probesCompleted, int probesSucceeded) { + mProbesCompleted = probesCompleted; + mProbesSucceeded = probesSucceeded; + } + + void notifyCapportApiDataChanged(CaptivePortalData data) { + try { + mNmCallbacks.notifyCaptivePortalDataChanged(data); + } catch (RemoteException e) { + throw new AssertionError("This cannot happen", e); + } + } + + public String waitForRedirectUrl() { + assertTrue(mNetworkStatusReceived.block(TIMEOUT_MS)); + return mRedirectUrl; + } + + public void expectDisconnected() { + expectDisconnected(TIMEOUT_MS); + } + + public void expectPreventReconnectReceived() { + expectPreventReconnectReceived(TIMEOUT_MS); + } + + void notifyDataStallSuspected() throws Exception { + final DataStallReportParcelable p = new DataStallReportParcelable(); + p.detectionMethod = DATA_STALL_DETECTION_METHOD; + p.timestampMillis = DATA_STALL_TIMESTAMP; + mNmCallbacks.notifyDataStallSuspected(p); + } + + public void setCreatedCallback(Runnable r) { + mCreatedCallback = r; + } + + public void setUnwantedCallback(Runnable r) { + mUnwantedCallback = r; + } + + public void setDisconnectedCallback(Runnable r) { + mDisconnectedCallback = r; + } + } + + /** + * A NetworkFactory that allows to wait until any in-flight NetworkRequest add or remove + * operations have been processed and test for them. + */ + private static class MockNetworkFactory extends NetworkFactory { + private final ConditionVariable mNetworkStartedCV = new ConditionVariable(); + private final ConditionVariable mNetworkStoppedCV = new ConditionVariable(); + private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false); + + static class RequestEntry { + @NonNull + public final NetworkRequest request; + + RequestEntry(@NonNull final NetworkRequest request) { + this.request = request; + } + + static final class Add extends RequestEntry { + public final int factorySerialNumber; + + Add(@NonNull final NetworkRequest request, final int factorySerialNumber) { + super(request); + this.factorySerialNumber = factorySerialNumber; + } + } + + static final class Remove extends RequestEntry { + Remove(@NonNull final NetworkRequest request) { + super(request); + } + } + } + + // History of received requests adds and removes. + private final ArrayTrackRecord.ReadHead mRequestHistory = + new ArrayTrackRecord().newReadHead(); + + private static T failIfNull(@Nullable final T obj, @Nullable final String message) { + if (null == obj) fail(null != message ? message : "Must not be null"); + return obj; + } + + + public RequestEntry.Add expectRequestAdd() { + return failIfNull((RequestEntry.Add) mRequestHistory.poll(TIMEOUT_MS, + it -> it instanceof RequestEntry.Add), "Expected request add"); + } + + public void expectRequestAdds(final int count) { + for (int i = count; i > 0; --i) { + expectRequestAdd(); + } + } + + public RequestEntry.Remove expectRequestRemove() { + return failIfNull((RequestEntry.Remove) mRequestHistory.poll(TIMEOUT_MS, + it -> it instanceof RequestEntry.Remove), "Expected request remove"); + } + + public void expectRequestRemoves(final int count) { + for (int i = count; i > 0; --i) { + expectRequestRemove(); + } + } + + // Used to collect the networks requests managed by this factory. This is a duplicate of + // the internal information stored in the NetworkFactory (which is private). + private SparseArray mNetworkRequests = new SparseArray<>(); + private final HandlerThread mHandlerSendingRequests; + + public MockNetworkFactory(Looper looper, Context context, String logTag, + NetworkCapabilities filter, HandlerThread threadSendingRequests) { + super(looper, context, logTag, filter); + mHandlerSendingRequests = threadSendingRequests; + } + + public int getMyRequestCount() { + return getRequestCount(); + } + + protected void startNetwork() { + mNetworkStarted.set(true); + mNetworkStartedCV.open(); + } + + protected void stopNetwork() { + mNetworkStarted.set(false); + mNetworkStoppedCV.open(); + } + + public boolean getMyStartRequested() { + return mNetworkStarted.get(); + } + + public ConditionVariable getNetworkStartedCV() { + mNetworkStartedCV.close(); + return mNetworkStartedCV; + } + + public ConditionVariable getNetworkStoppedCV() { + mNetworkStoppedCV.close(); + return mNetworkStoppedCV; + } + + @Override + protected void handleAddRequest(NetworkRequest request, int score, + int factorySerialNumber) { + mNetworkRequests.put(request.requestId, request); + super.handleAddRequest(request, score, factorySerialNumber); + mRequestHistory.add(new RequestEntry.Add(request, factorySerialNumber)); + } + + @Override + protected void handleRemoveRequest(NetworkRequest request) { + mNetworkRequests.remove(request.requestId); + super.handleRemoveRequest(request); + mRequestHistory.add(new RequestEntry.Remove(request)); + } + + public void assertRequestCountEquals(final int count) { + assertEquals(count, getMyRequestCount()); + } + + @Override + public void terminate() { + super.terminate(); + // Make sure there are no remaining requests unaccounted for. + HandlerUtils.waitForIdle(mHandlerSendingRequests, TIMEOUT_MS); + assertNull(mRequestHistory.poll(0, r -> true)); + } + + // Trigger releasing the request as unfulfillable + public void triggerUnfulfillable(NetworkRequest r) { + super.releaseRequestAsUnfulfillableByAnyFactory(r); + } + + public void assertNoRequestChanged() { + assertNull(mRequestHistory.poll(0, r -> true)); + } + } + + private Set uidRangesForUids(int... uids) { + final ArraySet ranges = new ArraySet<>(); + for (final int uid : uids) { + ranges.add(new UidRange(uid, uid)); + } + return ranges; + } + + private static Looper startHandlerThreadAndReturnLooper() { + final HandlerThread handlerThread = new HandlerThread("MockVpnThread"); + handlerThread.start(); + return handlerThread.getLooper(); + } + + private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork { + // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does + // not inherit from NetworkAgent. + private TestNetworkAgentWrapper mMockNetworkAgent; + private boolean mAgentRegistered = false; + + private int mVpnType = VpnManager.TYPE_VPN_SERVICE; + private UnderlyingNetworkInfo mUnderlyingNetworkInfo; + + // These ConditionVariables allow tests to wait for LegacyVpnRunner to be stopped/started. + // TODO: this scheme is ad-hoc and error-prone because it does not fail if, for example, the + // test expects two starts in a row, or even if the production code calls start twice in a + // row. find a better solution. Simply putting a method to create a LegacyVpnRunner into + // Vpn.Dependencies doesn't work because LegacyVpnRunner is not a static class and has + // extensive access into the internals of Vpn. + private ConditionVariable mStartLegacyVpnCv = new ConditionVariable(); + private ConditionVariable mStopVpnRunnerCv = new ConditionVariable(); + + public MockVpn(int userId) { + super(startHandlerThreadAndReturnLooper(), mServiceContext, + new Dependencies() { + @Override + public boolean isCallerSystem() { + return true; + } + + @Override + public DeviceIdleInternal getDeviceIdleInternal() { + return mDeviceIdleInternal; + } + }, + mNetworkManagementService, mMockNetd, userId, mVpnProfileStore); + } + + public void setUids(Set uids) { + mNetworkCapabilities.setUids(UidRange.toIntRanges(uids)); + if (mAgentRegistered) { + mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities, true); + } + } + + public void setVpnType(int vpnType) { + mVpnType = vpnType; + } + + @Override + public Network getNetwork() { + return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork(); + } + + @Override + public int getActiveVpnType() { + return mVpnType; + } + + private LinkProperties makeLinkProperties() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(VPN_IFNAME); + return lp; + } + + private void registerAgent(boolean isAlwaysMetered, Set uids, LinkProperties lp) + throws Exception { + if (mAgentRegistered) throw new IllegalStateException("already registered"); + updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent"); + mConfig = new VpnConfig(); + mConfig.session = "MySession12345"; + setUids(uids); + if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); + mInterface = VPN_IFNAME; + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType(), + mConfig.session)); + mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp, + mNetworkCapabilities); + mMockNetworkAgent.waitForIdle(TIMEOUT_MS); + + verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()), + eq(toUidRangeStableParcels(uids))); + verify(mMockNetd, never()) + .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()), any()); + mAgentRegistered = true; + verify(mMockNetd).networkCreate(nativeNetworkConfigVpn(getNetwork().netId, + !mMockNetworkAgent.isBypassableVpn(), mVpnType)); + updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent"); + mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); + mNetworkAgent = mMockNetworkAgent.getNetworkAgent(); + } + + private void registerAgent(Set uids) throws Exception { + registerAgent(false /* isAlwaysMetered */, uids, makeLinkProperties()); + } + + private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) { + mMockNetworkAgent.connect(validated, hasInternet, isStrictMode); + } + + private void connect(boolean validated) { + mMockNetworkAgent.connect(validated); + } + + private TestNetworkAgentWrapper getAgent() { + return mMockNetworkAgent; + } + + public void establish(LinkProperties lp, int uid, Set ranges, boolean validated, + boolean hasInternet, boolean isStrictMode) throws Exception { + mNetworkCapabilities.setOwnerUid(uid); + mNetworkCapabilities.setAdministratorUids(new int[]{uid}); + registerAgent(false, ranges, lp); + connect(validated, hasInternet, isStrictMode); + waitForIdle(); + } + + public void establish(LinkProperties lp, int uid, Set ranges) throws Exception { + establish(lp, uid, ranges, true, true, false); + } + + public void establishForMyUid(LinkProperties lp) throws Exception { + final int uid = Process.myUid(); + establish(lp, uid, uidRangesForUids(uid), true, true, false); + } + + public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode) + throws Exception { + final int uid = Process.myUid(); + establish(makeLinkProperties(), uid, uidRangesForUids(uid), validated, hasInternet, + isStrictMode); + } + + public void establishForMyUid() throws Exception { + establishForMyUid(makeLinkProperties()); + } + + public void sendLinkProperties(LinkProperties lp) { + mMockNetworkAgent.sendLinkProperties(lp); + } + + public void disconnect() { + if (mMockNetworkAgent != null) { + mMockNetworkAgent.disconnect(); + updateState(NetworkInfo.DetailedState.DISCONNECTED, "disconnect"); + } + mAgentRegistered = false; + setUids(null); + // Remove NET_CAPABILITY_INTERNET or MockNetworkAgent will refuse to connect later on. + mNetworkCapabilities.removeCapability(NET_CAPABILITY_INTERNET); + mInterface = null; + } + + @Override + public void startLegacyVpnRunner() { + mStartLegacyVpnCv.open(); + } + + public void expectStartLegacyVpnRunner() { + assertTrue("startLegacyVpnRunner not called after " + TIMEOUT_MS + " ms", + mStartLegacyVpnCv.block(TIMEOUT_MS)); + + // startLegacyVpn calls stopVpnRunnerPrivileged, which will open mStopVpnRunnerCv, just + // before calling startLegacyVpnRunner. Restore mStopVpnRunnerCv, so the test can expect + // that the VpnRunner is stopped and immediately restarted by calling + // expectStartLegacyVpnRunner() and expectStopVpnRunnerPrivileged() back-to-back. + mStopVpnRunnerCv = new ConditionVariable(); + } + + @Override + public void stopVpnRunnerPrivileged() { + if (mVpnRunner != null) { + super.stopVpnRunnerPrivileged(); + disconnect(); + mStartLegacyVpnCv = new ConditionVariable(); + } + mVpnRunner = null; + mStopVpnRunnerCv.open(); + } + + public void expectStopVpnRunnerPrivileged() { + assertTrue("stopVpnRunnerPrivileged not called after " + TIMEOUT_MS + " ms", + mStopVpnRunnerCv.block(TIMEOUT_MS)); + } + + @Override + public synchronized UnderlyingNetworkInfo getUnderlyingNetworkInfo() { + if (mUnderlyingNetworkInfo != null) return mUnderlyingNetworkInfo; + + return super.getUnderlyingNetworkInfo(); + } + + private synchronized void setUnderlyingNetworkInfo( + UnderlyingNetworkInfo underlyingNetworkInfo) { + mUnderlyingNetworkInfo = underlyingNetworkInfo; + } + } + + private UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set ranges) { + return ranges.stream().map( + r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new); + } + + private VpnManagerService makeVpnManagerService() { + final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() { + public int getCallingUid() { + return mDeps.getCallingUid(); + } + + public HandlerThread makeHandlerThread() { + return mVMSHandlerThread; + } + + @Override + public VpnProfileStore getVpnProfileStore() { + return mVpnProfileStore; + } + + public INetd getNetd() { + return mMockNetd; + } + + public INetworkManagementService getINetworkManagementService() { + return mNetworkManagementService; + } + }; + return new VpnManagerService(mServiceContext, deps); + } + + private void assertVpnTransportInfo(NetworkCapabilities nc, int type) { + assertNotNull(nc); + final TransportInfo ti = nc.getTransportInfo(); + assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti, + ti instanceof VpnTransportInfo); + assertEquals(type, ((VpnTransportInfo) ti).getType()); + + } + + private void processBroadcast(Intent intent) { + mServiceContext.sendBroadcast(intent); + HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS); + waitForIdle(); + } + + private void mockVpn(int uid) { + synchronized (mVpnManagerService.mVpns) { + int userId = UserHandle.getUserId(uid); + mMockVpn = new MockVpn(userId); + // Every running user always has a Vpn in the mVpns array, even if no VPN is running. + mVpnManagerService.mVpns.put(userId, mMockVpn); + } + } + + private void mockUidNetworkingBlocked() { + doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1)) + ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean()); + } + + private boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) { + final int blockedOnAllNetworksReason = (blockedReasons & ~BLOCKED_METERED_REASON_MASK); + if (blockedOnAllNetworksReason != BLOCKED_REASON_NONE) { + return true; + } + if (meteredNetwork) { + return blockedReasons != BLOCKED_REASON_NONE; + } + return false; + } + + private void setBlockedReasonChanged(int blockedReasons) { + mBlockedReasons = blockedReasons; + mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons); + } + + private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) { + return mService.getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; + } + + private static class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker { + volatile boolean mConfigRestrictsAvoidBadWifi; + volatile int mConfigMeteredMultipathPreference; + + WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) { + super(c, h, r); + } + + @Override + public boolean configRestrictsAvoidBadWifi() { + return mConfigRestrictsAvoidBadWifi; + } + + @Override + public int configMeteredMultipathPreference() { + return mConfigMeteredMultipathPreference; + } + } + + /** + * Wait up to TIMEOUT_MS for {@code conditionVariable} to open. + * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens. + */ + static private void waitFor(ConditionVariable conditionVariable) { + if (conditionVariable.block(TIMEOUT_MS)) { + return; + } + fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms"); + } + + private T doAsUid(final int uid, @NonNull final Supplier what) { + when(mDeps.getCallingUid()).thenReturn(uid); + try { + return what.get(); + } finally { + returnRealCallingUid(); + } + } + + private void doAsUid(final int uid, @NonNull final Runnable what) { + doAsUid(uid, () -> { + what.run(); return Void.TYPE; + }); + } + + private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback, + int uid) { + doAsUid(uid, () -> { + mCm.registerNetworkCallback(request, callback); + }); + } + + private void registerDefaultNetworkCallbackAsUid(@NonNull final NetworkCallback callback, + final int uid) { + doAsUid(uid, () -> { + mCm.registerDefaultNetworkCallback(callback); + waitForIdle(); + }); + } + + private interface ExceptionalRunnable { + void run() throws Exception; + } + + private void withPermission(String permission, ExceptionalRunnable r) throws Exception { + if (mServiceContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + r.run(); + return; + } + try { + mServiceContext.setPermission(permission, PERMISSION_GRANTED); + r.run(); + } finally { + mServiceContext.setPermission(permission, PERMISSION_DENIED); + } + } + + private static final int PRIMARY_USER = 0; + private static final UidRange PRIMARY_UIDRANGE = + UidRange.createForUser(UserHandle.of(PRIMARY_USER)); + private static final int APP1_UID = UserHandle.getUid(PRIMARY_USER, 10100); + private static final int APP2_UID = UserHandle.getUid(PRIMARY_USER, 10101); + private static final int VPN_UID = UserHandle.getUid(PRIMARY_USER, 10043); + private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER, "", + UserInfo.FLAG_PRIMARY); + private static final UserHandle PRIMARY_USER_HANDLE = new UserHandle(PRIMARY_USER); + + private static final int RESTRICTED_USER = 1; + private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "", + UserInfo.FLAG_RESTRICTED); + static { + RESTRICTED_USER_INFO.restrictedProfileParentId = PRIMARY_USER; + } + + @Before + public void setUp() throws Exception { + mNetIdManager = new TestNetIdManager(); + + mContext = InstrumentationRegistry.getContext(); + + MockitoAnnotations.initMocks(this); + + when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO)); + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE)); + when(mUserManager.getUserInfo(PRIMARY_USER)).thenReturn(PRIMARY_USER_INFO); + // canHaveRestrictedProfile does not take a userId. It applies to the userId of the context + // it was started from, i.e., PRIMARY_USER. + when(mUserManager.canHaveRestrictedProfile()).thenReturn(true); + when(mUserManager.getUserInfo(RESTRICTED_USER)).thenReturn(RESTRICTED_USER_INFO); + + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) + .thenReturn(applicationInfo); + when(mPackageManager.getTargetSdkVersion(anyString())) + .thenReturn(applicationInfo.targetSdkVersion); + when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]); + + // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not. + // http://b/25897652 . + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mockDefaultPackages(); + mockHasSystemFeature(FEATURE_WIFI, true); + mockHasSystemFeature(FEATURE_WIFI_DIRECT, true); + doReturn(true).when(mTelephonyManager).isDataCapable(); + + FakeSettingsProvider.clearSettingsProvider(); + mServiceContext = new MockContext(InstrumentationRegistry.getContext(), + new FakeSettingsProvider()); + mServiceContext.setUseRegisteredHandlers(true); + + mAlarmManagerThread = new HandlerThread("TestAlarmManager"); + mAlarmManagerThread.start(); + initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler()); + + mCsHandlerThread = new HandlerThread("TestConnectivityService"); + mVMSHandlerThread = new HandlerThread("TestVpnManagerService"); + mDeps = makeDependencies(); + returnRealCallingUid(); + mService = new ConnectivityService(mServiceContext, + mMockDnsResolver, + mock(IpConnectivityLog.class), + mMockNetd, + mDeps); + mService.mLingerDelayMs = TEST_LINGER_DELAY_MS; + mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS; + verify(mDeps).makeMultinetworkPolicyTracker(any(), any(), any()); + + final ArgumentCaptor policyCallbackCaptor = + ArgumentCaptor.forClass(NetworkPolicyCallback.class); + verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(), + policyCallbackCaptor.capture()); + mPolicyCallback = policyCallbackCaptor.getValue(); + + // Create local CM before sending system ready so that we can answer + // getSystemService() correctly. + mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService); + mService.systemReadyInternal(); + mVpnManagerService = makeVpnManagerService(); + mVpnManagerService.systemReady(); + mockVpn(Process.myUid()); + mCm.bindProcessToNetwork(null); + mQosCallbackTracker = mock(QosCallbackTracker.class); + + // Ensure that the default setting for Captive Portals is used for most tests + setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT); + setAlwaysOnNetworks(false); + setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); + } + + private void returnRealCallingUid() { + doAnswer((invocationOnMock) -> Binder.getCallingUid()).when(mDeps).getCallingUid(); + } + + private ConnectivityService.Dependencies makeDependencies() { + doReturn(false).when(mSystemProperties).getBoolean("ro.radio.noril", false); + final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class); + doReturn(mCsHandlerThread).when(deps).makeHandlerThread(); + doReturn(mNetIdManager).when(deps).makeNetIdManager(); + doReturn(mNetworkStack).when(deps).getNetworkStack(); + doReturn(mSystemProperties).when(deps).getSystemProperties(); + doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); + doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any()); + doAnswer(inv -> { + mPolicyTracker = new WrappedMultinetworkPolicyTracker( + inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); + return mPolicyTracker; + }).when(deps).makeMultinetworkPolicyTracker(any(), any(), any()); + doReturn(true).when(deps).getCellular464XlatEnabled(); + + doReturn(60000).when(mResources).getInteger(R.integer.config_networkTransitionTimeout); + doReturn("").when(mResources).getString(R.string.config_networkCaptivePortalServerUrl); + doReturn(new String[]{ WIFI_WOL_IFNAME }).when(mResources).getStringArray( + R.array.config_wakeonlan_supported_interfaces); + doReturn(new String[] { "0,1", "1,3" }).when(mResources).getStringArray( + R.array.config_networkSupportedKeepaliveCount); + doReturn(new String[0]).when(mResources).getStringArray( + R.array.config_networkNotifySwitches); + doReturn(new int[]{10, 11, 12, 14, 15}).when(mResources).getIntArray( + R.array.config_protectedNetworks); + // We don't test the actual notification value strings, so just return an empty array. + // It doesn't matter what the values are as long as it's not null. + doReturn(new String[0]).when(mResources).getStringArray(R.array.network_switch_type_name); + + doReturn(R.array.config_networkSupportedKeepaliveCount).when(mResources) + .getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any()); + doReturn(R.array.network_switch_type_name).when(mResources) + .getIdentifier(eq("network_switch_type_name"), eq("array"), any()); + + + final ConnectivityResources connRes = mock(ConnectivityResources.class); + doReturn(mResources).when(connRes).get(); + doReturn(connRes).when(deps).getResources(any()); + + final Context mockResContext = mock(Context.class); + doReturn(mResources).when(mockResContext).getResources(); + ConnectivityResources.setResourcesContextForTest(mockResContext); + + return deps; + } + + private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) { + doAnswer(inv -> { + final long when = inv.getArgument(1); + final WakeupMessage wakeupMsg = inv.getArgument(3); + final Handler handler = inv.getArgument(4); + + long delayMs = when - SystemClock.elapsedRealtime(); + if (delayMs < 0) delayMs = 0; + if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) { + fail("Attempting to send msg more than " + UNREASONABLY_LONG_ALARM_WAIT_MS + + "ms into the future: " + delayMs); + } + alarmHandler.postDelayed(() -> handler.post(wakeupMsg::onAlarm), wakeupMsg /* token */, + delayMs); + + return null; + }).when(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(), + any(WakeupMessage.class), any()); + + doAnswer(inv -> { + final WakeupMessage wakeupMsg = inv.getArgument(0); + alarmHandler.removeCallbacksAndMessages(wakeupMsg /* token */); + return null; + }).when(am).cancel(any(WakeupMessage.class)); + } + + @After + public void tearDown() throws Exception { + unregisterDefaultNetworkCallbacks(); + maybeTearDownEnterpriseNetwork(); + setAlwaysOnNetworks(false); + if (mCellNetworkAgent != null) { + mCellNetworkAgent.disconnect(); + mCellNetworkAgent = null; + } + if (mWiFiNetworkAgent != null) { + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent = null; + } + if (mEthernetNetworkAgent != null) { + mEthernetNetworkAgent.disconnect(); + mEthernetNetworkAgent = null; + } + + if (mQosCallbackMockHelper != null) { + mQosCallbackMockHelper.tearDown(); + mQosCallbackMockHelper = null; + } + mMockVpn.disconnect(); + waitForIdle(); + + FakeSettingsProvider.clearSettingsProvider(); + ConnectivityResources.setResourcesContextForTest(null); + + mCsHandlerThread.quitSafely(); + mAlarmManagerThread.quitSafely(); + } + + private void mockDefaultPackages() throws Exception { + final String myPackageName = mContext.getPackageName(); + final PackageInfo myPackageInfo = mContext.getPackageManager().getPackageInfo( + myPackageName, PackageManager.GET_PERMISSIONS); + when(mPackageManager.getPackagesForUid(Binder.getCallingUid())).thenReturn( + new String[] {myPackageName}); + when(mPackageManager.getPackageInfoAsUser(eq(myPackageName), anyInt(), + eq(UserHandle.getCallingUserId()))).thenReturn(myPackageInfo); + + when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn( + Arrays.asList(new PackageInfo[] { + buildPackageInfo(/* SYSTEM */ false, APP1_UID), + buildPackageInfo(/* SYSTEM */ false, APP2_UID), + buildPackageInfo(/* SYSTEM */ false, VPN_UID) + })); + + // Create a fake always-on VPN package. + final int userId = UserHandle.getCallingUserId(); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; // Always-on supported in N+. + when(mPackageManager.getApplicationInfoAsUser(eq(ALWAYS_ON_PACKAGE), anyInt(), + eq(userId))).thenReturn(applicationInfo); + + // Minimal mocking to keep Vpn#isAlwaysOnPackageSupported happy. + ResolveInfo rInfo = new ResolveInfo(); + rInfo.serviceInfo = new ServiceInfo(); + rInfo.serviceInfo.metaData = new Bundle(); + final List services = Arrays.asList(new ResolveInfo[]{rInfo}); + when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA), + eq(userId))).thenReturn(services); + when(mPackageManager.getPackageUidAsUser(TEST_PACKAGE_NAME, userId)) + .thenReturn(Process.myUid()); + when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, userId)) + .thenReturn(VPN_UID); + } + + private void verifyActiveNetwork(int transport) { + // Test getActiveNetworkInfo() + assertNotNull(mCm.getActiveNetworkInfo()); + assertEquals(transportToLegacyType(transport), mCm.getActiveNetworkInfo().getType()); + // Test getActiveNetwork() + assertNotNull(mCm.getActiveNetwork()); + assertEquals(mCm.getActiveNetwork(), mCm.getActiveNetworkForUid(Process.myUid())); + if (!NetworkCapabilities.isValidTransport(transport)) { + throw new IllegalStateException("Unknown transport " + transport); + } + switch (transport) { + case TRANSPORT_WIFI: + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + break; + case TRANSPORT_CELLULAR: + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + break; + case TRANSPORT_ETHERNET: + assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + break; + default: + break; + } + // Test getNetworkInfo(Network) + assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork())); + assertEquals(transportToLegacyType(transport), + mCm.getNetworkInfo(mCm.getActiveNetwork()).getType()); + assertNotNull(mCm.getActiveNetworkInfoForUid(Process.myUid())); + // Test getNetworkCapabilities(Network) + assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork())); + assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport)); + } + + private void verifyNoNetwork() { + waitForIdle(); + // Test getActiveNetworkInfo() + assertNull(mCm.getActiveNetworkInfo()); + // Test getActiveNetwork() + assertNull(mCm.getActiveNetwork()); + assertNull(mCm.getActiveNetworkForUid(Process.myUid())); + // Test getAllNetworks() + assertEmpty(mCm.getAllNetworks()); + assertEmpty(mCm.getAllNetworkStateSnapshots()); + } + + /** + * Class to simplify expecting broadcasts using BroadcastInterceptingContext. + * Ensures that the receiver is unregistered after the expected broadcast is received. This + * cannot be done in the BroadcastReceiver itself because BroadcastInterceptingContext runs + * the receivers' receive method while iterating over the list of receivers, and unregistering + * the receiver during iteration throws ConcurrentModificationException. + */ + private class ExpectedBroadcast extends CompletableFuture { + private final BroadcastReceiver mReceiver; + + ExpectedBroadcast(BroadcastReceiver receiver) { + mReceiver = receiver; + } + + public Intent expectBroadcast(int timeoutMs) throws Exception { + try { + return get(timeoutMs, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + fail("Expected broadcast not received after " + timeoutMs + " ms"); + return null; + } finally { + mServiceContext.unregisterReceiver(mReceiver); + } + } + + public Intent expectBroadcast() throws Exception { + return expectBroadcast(BROADCAST_TIMEOUT_MS); + } + + public void expectNoBroadcast(int timeoutMs) throws Exception { + waitForIdle(); + try { + final Intent intent = get(timeoutMs, TimeUnit.MILLISECONDS); + fail("Unexpected broadcast: " + intent.getAction() + " " + intent.getExtras()); + } catch (TimeoutException expected) { + } finally { + mServiceContext.unregisterReceiver(mReceiver); + } + } + } + + /** Expects that {@code count} CONNECTIVITY_ACTION broadcasts are received. */ + private ExpectedBroadcast registerConnectivityBroadcast(final int count) { + return registerConnectivityBroadcastThat(count, intent -> true); + } + + private ExpectedBroadcast registerConnectivityBroadcastThat(final int count, + @NonNull final Predicate filter) { + final IntentFilter intentFilter = new IntentFilter(CONNECTIVITY_ACTION); + // AtomicReference allows receiver to access expected even though it is constructed later. + final AtomicReference expectedRef = new AtomicReference<>(); + final BroadcastReceiver receiver = new BroadcastReceiver() { + private int mRemaining = count; + public void onReceive(Context context, Intent intent) { + final int type = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1); + final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO); + Log.d(TAG, "Received CONNECTIVITY_ACTION type=" + type + " ni=" + ni); + if (!filter.test(intent)) return; + if (--mRemaining == 0) { + expectedRef.get().complete(intent); + } + } + }; + final ExpectedBroadcast expected = new ExpectedBroadcast(receiver); + expectedRef.set(expected); + mServiceContext.registerReceiver(receiver, intentFilter); + return expected; + } + + private boolean extraInfoInBroadcastHasExpectedNullness(NetworkInfo ni) { + final DetailedState state = ni.getDetailedState(); + if (state == DetailedState.CONNECTED && ni.getExtraInfo() == null) return false; + // Expect a null extraInfo if the network is CONNECTING, because a CONNECTIVITY_ACTION + // broadcast with a state of CONNECTING only happens due to legacy VPN lockdown, which also + // nulls out extraInfo. + if (state == DetailedState.CONNECTING && ni.getExtraInfo() != null) return false; + // Can't make any assertions about DISCONNECTED broadcasts. When a network actually + // disconnects, disconnectAndDestroyNetwork sets its state to DISCONNECTED and its extraInfo + // to null. But if the DISCONNECTED broadcast is just simulated by LegacyTypeTracker due to + // a network switch, extraInfo will likely be populated. + // This is likely a bug in CS, but likely not one we can fix without impacting apps. + return true; + } + + private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) { + return registerConnectivityBroadcastThat(1, intent -> { + final int actualType = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1); + final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO); + return type == actualType + && state == ni.getDetailedState() + && extraInfoInBroadcastHasExpectedNullness(ni); + }); + } + + @Test + public void testNetworkTypes() { + // Ensure that our mocks for the networkAttributes config variable work as expected. If they + // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types + // will fail. Failing here is much easier to debug. + assertTrue(mCm.isNetworkSupported(TYPE_WIFI)); + assertTrue(mCm.isNetworkSupported(TYPE_MOBILE)); + assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS)); + assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA)); + assertFalse(mCm.isNetworkSupported(TYPE_PROXY)); + + // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our + // mocks, this assert exercises the ConnectivityService code path that ensures that + // TYPE_ETHERNET is supported if the ethernet service is running. + assertTrue(mCm.isNetworkSupported(TYPE_ETHERNET)); + } + + @Test + public void testNetworkFeature() throws Exception { + // Connect the cell agent and wait for the connected broadcast. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(NET_CAPABILITY_SUPL); + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + + // Build legacy request for SUPL. + final NetworkCapabilities legacyCaps = new NetworkCapabilities(); + legacyCaps.addTransportType(TRANSPORT_CELLULAR); + legacyCaps.addCapability(NET_CAPABILITY_SUPL); + final NetworkRequest legacyRequest = new NetworkRequest(legacyCaps, TYPE_MOBILE_SUPL, + ConnectivityManager.REQUEST_ID_UNSET, NetworkRequest.Type.REQUEST); + + // File request, withdraw it and make sure no broadcast is sent + b = registerConnectivityBroadcast(1); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.requestNetwork(legacyRequest, callback); + callback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); + b.expectNoBroadcast(800); // 800ms long enough to at least flake if this is sent + + // Disconnect the network and expect mobile disconnected broadcast. + b = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); + mCellNetworkAgent.disconnect(); + b.expectBroadcast(); + } + + @Test + public void testLingering() throws Exception { + verifyNoNetwork(); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + assertNull(mCm.getActiveNetworkInfo()); + assertNull(mCm.getActiveNetwork()); + // Test bringing up validated cellular. + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + assertLength(2, mCm.getAllNetworks()); + assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || + mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork())); + assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) || + mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork())); + // Test bringing up validated WiFi. + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + assertLength(2, mCm.getAllNetworks()); + assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || + mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork())); + assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) || + mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork())); + // Test cellular linger timeout. + mCellNetworkAgent.expectDisconnected(); + waitForIdle(); + assertLength(1, mCm.getAllNetworks()); + verifyActiveNetwork(TRANSPORT_WIFI); + assertLength(1, mCm.getAllNetworks()); + assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork()); + // Test WiFi disconnect. + b = registerConnectivityBroadcast(1); + mWiFiNetworkAgent.disconnect(); + b.expectBroadcast(); + verifyNoNetwork(); + } + + /** + * Verify a newly created network will be inactive instead of torn down even if no one is + * requesting. + */ + @Test + public void testNewNetworkInactive() throws Exception { + // Create a callback that monitoring the testing network. + final TestNetworkCallback listenCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(new NetworkRequest.Builder().build(), listenCallback); + + // 1. Create a network that is not requested by anyone, and does not satisfy any of the + // default requests. Verify that the network will be inactive instead of torn down. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connectWithoutInternet(); + listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + listenCallback.assertNoCallback(); + + // Verify that the network will be torn down after nascent expiry. A small period of time + // is added in case of flakiness. + final int nascentTimeoutMs = + mService.mNascentDelayMs + mService.mNascentDelayMs / 4; + listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent, nascentTimeoutMs); + + // 2. Create a network that is satisfied by a request comes later. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connectWithoutInternet(); + listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + final TestNetworkCallback wifiCallback = new TestNetworkCallback(); + mCm.requestNetwork(wifiRequest, wifiCallback); + wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + // Verify that the network will be kept since the request is still satisfied. And is able + // to get disconnected as usual if the request is released after the nascent timer expires. + listenCallback.assertNoCallback(nascentTimeoutMs); + mCm.unregisterNetworkCallback(wifiCallback); + listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // 3. Create a network that is satisfied by a request comes later. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connectWithoutInternet(); + listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + mCm.requestNetwork(wifiRequest, wifiCallback); + wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + // Verify that the network will still be torn down after the request gets removed. + mCm.unregisterNetworkCallback(wifiCallback); + listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // There is no need to ensure that LOSING is never sent in the common case that the + // network immediately satisfies a request that was already present, because it is already + // verified anywhere whenever {@code TestNetworkCallback#expectAvailable*} is called. + + mCm.unregisterNetworkCallback(listenCallback); + } + + /** + * Verify a newly created network will be inactive and switch to background if only background + * request is satisfied. + */ + @Test + public void testNewNetworkInactive_bgNetwork() throws Exception { + // Create a callback that monitoring the wifi network. + final TestNetworkCallback wifiListenCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(), wifiListenCallback); + + // Create callbacks that can monitor background and foreground mobile networks. + // This is done by granting using background networks permission before registration. Thus, + // the service will not add {@code NET_CAPABILITY_FOREGROUND} by default. + grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); + final TestNetworkCallback bgMobileListenCallback = new TestNetworkCallback(); + final TestNetworkCallback fgMobileListenCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(), bgMobileListenCallback); + mCm.registerNetworkCallback(new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_FOREGROUND).build(), fgMobileListenCallback); + + // Connect wifi, which satisfies default request. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + wifiListenCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + + // Connect a cellular network, verify that satisfies only the background callback. + setAlwaysOnNetworks(true); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + bgMobileListenCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + fgMobileListenCallback.assertNoCallback(); + assertFalse(isForegroundNetwork(mCellNetworkAgent)); + + mCellNetworkAgent.disconnect(); + bgMobileListenCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + fgMobileListenCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(wifiListenCallback); + mCm.unregisterNetworkCallback(bgMobileListenCallback); + mCm.unregisterNetworkCallback(fgMobileListenCallback); + } + + @Test + public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception { + // Test bringing up unvalidated WiFi + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + ExpectedBroadcast b = registerConnectivityBroadcast(1); + mWiFiNetworkAgent.connect(false); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test bringing up unvalidated cellular + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false); + waitForIdle(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test cellular disconnect. + mCellNetworkAgent.disconnect(); + waitForIdle(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test bringing up validated cellular + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + b = registerConnectivityBroadcast(2); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test cellular disconnect. + b = registerConnectivityBroadcast(2); + mCellNetworkAgent.disconnect(); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test WiFi disconnect. + b = registerConnectivityBroadcast(1); + mWiFiNetworkAgent.disconnect(); + b.expectBroadcast(); + verifyNoNetwork(); + } + + @Test + public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception { + // Test bringing up unvalidated cellular. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + ExpectedBroadcast b = registerConnectivityBroadcast(1); + mCellNetworkAgent.connect(false); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test bringing up unvalidated WiFi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent.connect(false); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test WiFi disconnect. + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent.disconnect(); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test cellular disconnect. + b = registerConnectivityBroadcast(1); + mCellNetworkAgent.disconnect(); + b.expectBroadcast(); + verifyNoNetwork(); + } + + @Test + public void testUnlingeringDoesNotValidate() throws Exception { + // Test bringing up unvalidated WiFi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + ExpectedBroadcast b = registerConnectivityBroadcast(1); + mWiFiNetworkAgent.connect(false); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + // Test bringing up validated cellular. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + b = registerConnectivityBroadcast(2); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + // Test cellular disconnect. + b = registerConnectivityBroadcast(2); + mCellNetworkAgent.disconnect(); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Unlingering a network should not cause it to be marked as validated. + assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + } + + @Test + public void testCellularOutscoresWeakWifi() throws Exception { + // Test bringing up validated cellular. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + ExpectedBroadcast b = registerConnectivityBroadcast(1); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test bringing up validated WiFi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test WiFi getting really weak. + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent.adjustScore(-11); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test WiFi restoring signal strength. + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent.adjustScore(11); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + } + + @Test + public void testReapingNetwork() throws Exception { + // Test bringing up WiFi without NET_CAPABILITY_INTERNET. + // Expect it to be torn down immediately because it satisfies no requests. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connectWithoutInternet(); + mWiFiNetworkAgent.expectDisconnected(); + // Test bringing up cellular without NET_CAPABILITY_INTERNET. + // Expect it to be torn down immediately because it satisfies no requests. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mCellNetworkAgent.connectWithoutInternet(); + mCellNetworkAgent.expectDisconnected(); + // Test bringing up validated WiFi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); + mWiFiNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test bringing up unvalidated cellular. + // Expect it to be torn down because it could never be the highest scoring network + // satisfying the default request even if it validated. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false); + mCellNetworkAgent.expectDisconnected(); + verifyActiveNetwork(TRANSPORT_WIFI); + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent.expectDisconnected(); + } + + @Test + public void testCellularFallback() throws Exception { + // Test bringing up validated cellular. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + ExpectedBroadcast b = registerConnectivityBroadcast(1); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Test bringing up validated WiFi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Reevaluate WiFi (it'll instantly fail DNS). + b = registerConnectivityBroadcast(2); + assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork()); + // Should quickly fall back to Cellular. + b.expectBroadcast(); + assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Reevaluate cellular (it'll instantly fail DNS). + b = registerConnectivityBroadcast(2); + assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); + // Should quickly fall back to WiFi. + b.expectBroadcast(); + assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + verifyActiveNetwork(TRANSPORT_WIFI); + } + + @Test + public void testWiFiFallback() throws Exception { + // Test bringing up unvalidated WiFi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + ExpectedBroadcast b = registerConnectivityBroadcast(1); + mWiFiNetworkAgent.connect(false); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + // Test bringing up validated cellular. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + b = registerConnectivityBroadcast(2); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + // Reevaluate cellular (it'll instantly fail DNS). + b = registerConnectivityBroadcast(2); + assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); + // Should quickly fall back to WiFi. + b.expectBroadcast(); + assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( + NET_CAPABILITY_VALIDATED)); + verifyActiveNetwork(TRANSPORT_WIFI); + } + + @Test + public void testRequiresValidation() { + assertTrue(NetworkMonitorUtils.isValidationRequired( + mCm.getDefaultRequest().networkCapabilities)); + } + + /** + * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks + * this class receives, by calling expectCallback() exactly once each time a callback is + * received. assertNoCallback may be called at any time. + */ + private class TestNetworkCallback extends TestableNetworkCallback { + TestNetworkCallback() { + super(TEST_CALLBACK_TIMEOUT_MS); + } + + @Override + public void assertNoCallback() { + // TODO: better support this use case in TestableNetworkCallback + waitForIdle(); + assertNoCallback(0 /* timeout */); + } + + @Override + public T expectCallback(final KClass type, final HasNetwork n, + final long timeoutMs) { + final T callback = super.expectCallback(type, n, timeoutMs); + if (callback instanceof CallbackEntry.Losing) { + // TODO : move this to the specific test(s) needing this rather than here. + final CallbackEntry.Losing losing = (CallbackEntry.Losing) callback; + final int maxMsToLive = losing.getMaxMsToLive(); + String msg = String.format( + "Invalid linger time value %d, must be between %d and %d", + maxMsToLive, 0, mService.mLingerDelayMs); + assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= mService.mLingerDelayMs); + } + return callback; + } + } + + // Can't be part of TestNetworkCallback because "cannot be declared static; static methods can + // only be declared in a static or top level type". + static void assertNoCallbacks(TestNetworkCallback ... callbacks) { + for (TestNetworkCallback c : callbacks) { + c.assertNoCallback(); + } + } + + static void expectOnLost(TestNetworkAgentWrapper network, TestNetworkCallback ... callbacks) { + for (TestNetworkCallback c : callbacks) { + c.expectCallback(CallbackEntry.LOST, network); + } + } + + static void expectAvailableCallbacksUnvalidatedWithSpecifier(TestNetworkAgentWrapper network, + NetworkSpecifier specifier, TestNetworkCallback ... callbacks) { + for (TestNetworkCallback c : callbacks) { + c.expectCallback(CallbackEntry.AVAILABLE, network); + c.expectCapabilitiesThat(network, (nc) -> + !nc.hasCapability(NET_CAPABILITY_VALIDATED) + && Objects.equals(specifier, nc.getNetworkSpecifier())); + c.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, network); + c.expectCallback(CallbackEntry.BLOCKED_STATUS, network); + } + } + + @Test + public void testStateChangeNetworkCallbacks() throws Exception { + final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest genericRequest = new NetworkRequest.Builder() + .clearCapabilities().build(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); + mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); + mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); + + // Test unvalidated networks + ExpectedBroadcast b = registerConnectivityBroadcast(1); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + b.expectBroadcast(); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + + // This should not trigger spurious onAvailable() callbacks, b/21762680. + mCellNetworkAgent.adjustScore(-1); + waitForIdle(); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + b.expectBroadcast(); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + + b = registerConnectivityBroadcast(2); + mWiFiNetworkAgent.disconnect(); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + cellNetworkCallback.assertNoCallback(); + b.expectBroadcast(); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + + b = registerConnectivityBroadcast(1); + mCellNetworkAgent.disconnect(); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + b.expectBroadcast(); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + + // Test validated networks + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + + // This should not trigger spurious onAvailable() callbacks, b/21762680. + mCellNetworkAgent.adjustScore(-1); + waitForIdle(); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + genericNetworkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + + mWiFiNetworkAgent.disconnect(); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + + mCellNetworkAgent.disconnect(); + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); + } + + private void doNetworkCallbacksSanitizationTest(boolean sanitized) throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + mCm.registerNetworkCallback(wifiRequest, callback); + mCm.registerDefaultNetworkCallback(defaultCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + final LinkProperties newLp = new LinkProperties(); + final Uri capportUrl = Uri.parse("https://capport.example.com/api"); + final CaptivePortalData capportData = new CaptivePortalData.Builder() + .setCaptive(true).build(); + + final Uri expectedCapportUrl = sanitized ? null : capportUrl; + newLp.setCaptivePortalApiUrl(capportUrl); + mWiFiNetworkAgent.sendLinkProperties(newLp); + callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); + defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); + + final CaptivePortalData expectedCapportData = sanitized ? null : capportData; + mWiFiNetworkAgent.notifyCapportApiDataChanged(capportData); + callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> + Objects.equals(expectedCapportData, lp.getCaptivePortalData())); + + final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork()); + assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl()); + assertEquals(expectedCapportData, lp.getCaptivePortalData()); + } + + @Test + public void networkCallbacksSanitizationTest_Sanitize() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_DENIED); + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); + doNetworkCallbacksSanitizationTest(true /* sanitized */); + } + + @Test + public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_GRANTED); + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); + doNetworkCallbacksSanitizationTest(false /* sanitized */); + } + + @Test + public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_DENIED); + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + doNetworkCallbacksSanitizationTest(false /* sanitized */); + } + + @Test + public void testOwnerUidCannotChange() throws Exception { + final NetworkCapabilities ncTemplate = new NetworkCapabilities(); + final int originalOwnerUid = Process.myUid(); + ncTemplate.setOwnerUid(originalOwnerUid); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), + ncTemplate); + mWiFiNetworkAgent.connect(false); + waitForIdle(); + + // Send ConnectivityService an update to the mWiFiNetworkAgent's capabilities that changes + // the owner UID and an unrelated capability. + NetworkCapabilities agentCapabilities = mWiFiNetworkAgent.getNetworkCapabilities(); + assertEquals(originalOwnerUid, agentCapabilities.getOwnerUid()); + agentCapabilities.setOwnerUid(42); + assertFalse(agentCapabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + agentCapabilities.addCapability(NET_CAPABILITY_NOT_CONGESTED); + mWiFiNetworkAgent.setNetworkCapabilities(agentCapabilities, true); + waitForIdle(); + + // Owner UIDs are not visible without location permission. + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + // Check that the capability change has been applied but the owner UID is not modified. + NetworkCapabilities nc = mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()); + assertEquals(originalOwnerUid, nc.getOwnerUid()); + assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); + } + + @Test + public void testMultipleLingering() throws Exception { + // This test would be flaky with the default 120ms timer: that is short enough that + // lingered networks are torn down before assertions can be run. We don't want to mock the + // lingering timer to keep the WakeupMessage logic realistic: this has already proven useful + // in detecting races. + mService.mLingerDelayMs = 300; + + NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities().addCapability(NET_CAPABILITY_NOT_METERED) + .build(); + TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mWiFiNetworkAgent.connect(true); + // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request. + // We then get LOSING when wifi validates and cell is outscored. + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + // TODO: Investigate sending validated before losing. + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mEthernetNetworkAgent.connect(true); + callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); + // TODO: Investigate sending validated before losing. + callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); + assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mEthernetNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + for (int i = 0; i < 4; i++) { + TestNetworkAgentWrapper oldNetwork, newNetwork; + if (i % 2 == 0) { + mWiFiNetworkAgent.adjustScore(-15); + oldNetwork = mWiFiNetworkAgent; + newNetwork = mCellNetworkAgent; + } else { + mWiFiNetworkAgent.adjustScore(15); + oldNetwork = mCellNetworkAgent; + newNetwork = mWiFiNetworkAgent; + + } + callback.expectCallback(CallbackEntry.LOSING, oldNetwork); + // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no + // longer lingering? + defaultCallback.expectAvailableCallbacksValidated(newNetwork); + assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork()); + } + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Verify that if a network no longer satisfies a request, we send LOST and not LOSING, even + // if the network is still up. + mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + // We expect a notification about the capabilities change, and nothing else. + defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); + defaultCallback.assertNoCallback(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Wifi no longer satisfies our listen, which is for an unmetered network. + // But because its score is 55, it's still up (and the default network). + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Disconnect our test networks. + mWiFiNetworkAgent.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + mCellNetworkAgent.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + waitForIdle(); + assertEquals(null, mCm.getActiveNetwork()); + + mCm.unregisterNetworkCallback(callback); + waitForIdle(); + + // Check that a network is only lingered or torn down if it would not satisfy a request even + // if it validated. + request = new NetworkRequest.Builder().clearCapabilities().build(); + callback = new TestNetworkCallback(); + + mCm.registerNetworkCallback(request, callback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false); // Score: 10 + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Bring up wifi with a score of 20. + // Cell stays up because it would satisfy the default request if it validated. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); // Score: 20 + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Bring up wifi with a score of 70. + // Cell is lingered because it would not satisfy any request, even if it validated. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.adjustScore(50); + mWiFiNetworkAgent.connect(false); // Score: 70 + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Tear down wifi. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but + // it's arguably correct to linger it, since it was the default network before it validated. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + // TODO: Investigate sending validated before losing. + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + mCellNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + waitForIdle(); + assertEquals(null, mCm.getActiveNetwork()); + + // If a network is lingering, and we add and remove a request from it, resume lingering. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + // TODO: Investigate sending validated before losing. + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + NetworkCallback noopCallback = new NetworkCallback(); + mCm.requestNetwork(cellRequest, noopCallback); + // TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer + // lingering? + mCm.unregisterNetworkCallback(noopCallback); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + + // Similar to the above: lingering can start even after the lingered request is removed. + // Disconnect wifi and switch to cell. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Cell is now the default network. Pin it with a cell-specific request. + noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525 + mCm.requestNetwork(cellRequest, noopCallback); + + // Now connect wifi, and expect it to become the default network. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + // The default request is lingering on cell, but nothing happens to cell, and we send no + // callbacks for it, because it's kept up by cellRequest. + callback.assertNoCallback(); + // Now unregister cellRequest and expect cell to start lingering. + mCm.unregisterNetworkCallback(noopCallback); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + + // Let linger run its course. + callback.assertNoCallback(); + final int lingerTimeoutMs = mService.mLingerDelayMs + mService.mLingerDelayMs / 4; + callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, lingerTimeoutMs); + + // Register a TRACK_DEFAULT request and check that it does not affect lingering. + TestNetworkCallback trackDefaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(trackDefaultCallback); + trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + mEthernetNetworkAgent.connect(true); + callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); + trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Let linger run its course. + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent, lingerTimeoutMs); + + // Clean up. + mEthernetNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + trackDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + + mCm.unregisterNetworkCallback(callback); + mCm.unregisterNetworkCallback(defaultCallback); + mCm.unregisterNetworkCallback(trackDefaultCallback); + } + + private void grantUsingBackgroundNetworksPermissionForUid(final int uid) throws Exception { + grantUsingBackgroundNetworksPermissionForUid(uid, mContext.getPackageName()); + } + + private void grantUsingBackgroundNetworksPermissionForUid( + final int uid, final String packageName) throws Exception { + when(mPackageManager.getPackageInfo( + eq(packageName), eq(GET_PERMISSIONS | MATCH_ANY_USER))) + .thenReturn(buildPackageInfo(true /* hasSystemPermission */, uid)); + mService.mPermissionMonitor.onPackageAdded(packageName, uid); + } + + @Test + public void testNetworkGoesIntoBackgroundAfterLinger() throws Exception { + setAlwaysOnNetworks(true); + grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); + NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities() + .build(); + TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + // Wifi comes up and cell lingers. + mWiFiNetworkAgent.connect(true); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + + // File a request for cellular, then release it. + NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + NetworkCallback noopCallback = new NetworkCallback(); + mCm.requestNetwork(cellRequest, noopCallback); + mCm.unregisterNetworkCallback(noopCallback); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + + // Let linger run its course. + callback.assertNoCallback(); + final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4; + callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent, + lingerTimeoutMs); + + // Clean up. + mCm.unregisterNetworkCallback(defaultCallback); + mCm.unregisterNetworkCallback(callback); + } + + private NativeNetworkConfig nativeNetworkConfigPhysical(int netId, int permission) { + return new NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission, + /*secure=*/ false, VpnManager.TYPE_VPN_NONE); + } + + private NativeNetworkConfig nativeNetworkConfigVpn(int netId, boolean secure, int vpnType) { + return new NativeNetworkConfig(netId, NativeNetworkType.VIRTUAL, INetd.PERMISSION_NONE, + secure, vpnType); + } + + @Test + public void testNetworkAgentCallbacks() throws Exception { + // Keeps track of the order of events that happen in this test. + final LinkedBlockingQueue eventOrder = new LinkedBlockingQueue<>(); + + final NetworkRequest request = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + final AtomicReference wifiNetwork = new AtomicReference<>(); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + + // Expectations for state when various callbacks fire. These expectations run on the handler + // thread and not on the test thread because they need to prevent the handler thread from + // advancing while they examine state. + + // 1. When onCreated fires, netd has been told to create the network. + mWiFiNetworkAgent.setCreatedCallback(() -> { + eventOrder.offer("onNetworkCreated"); + wifiNetwork.set(mWiFiNetworkAgent.getNetwork()); + assertNotNull(wifiNetwork.get()); + try { + verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( + wifiNetwork.get().getNetId(), INetd.PERMISSION_NONE)); + } catch (RemoteException impossible) { + fail(); + } + }); + + // 2. onNetworkUnwanted isn't precisely ordered with respect to any particular events. Just + // check that it is fired at some point after disconnect. + mWiFiNetworkAgent.setUnwantedCallback(() -> eventOrder.offer("onNetworkUnwanted")); + + // 3. While the teardown timer is running, connectivity APIs report the network is gone, but + // netd has not yet been told to destroy it. + final Runnable duringTeardown = () -> { + eventOrder.offer("timePasses"); + assertNull(mCm.getLinkProperties(wifiNetwork.get())); + try { + verify(mMockNetd, never()).networkDestroy(wifiNetwork.get().getNetId()); + } catch (RemoteException impossible) { + fail(); + } + }; + + // 4. After onNetworkDisconnected is called, connectivity APIs report the network is gone, + // and netd has been told to destroy it. + mWiFiNetworkAgent.setDisconnectedCallback(() -> { + eventOrder.offer("onNetworkDisconnected"); + assertNull(mCm.getLinkProperties(wifiNetwork.get())); + try { + verify(mMockNetd).networkDestroy(wifiNetwork.get().getNetId()); + } catch (RemoteException impossible) { + fail(); + } + }); + + // Connect a network, and file a request for it after it has come up, to ensure the nascent + // timer is cleared and the test does not have to wait for it. Filing the request after the + // network has come up is necessary because ConnectivityService does not appear to clear the + // nascent timer if the first request satisfied by the network was filed before the network + // connected. + // TODO: fix this bug, file the request before connecting, and remove the waitForIdle. + mWiFiNetworkAgent.connectWithoutInternet(); + waitForIdle(); + mCm.requestNetwork(request, callback); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + // Set teardown delay and make sure CS has processed it. + mWiFiNetworkAgent.getNetworkAgent().setTeardownDelayMillis(300); + waitForIdle(); + + // Post the duringTeardown lambda to the handler so it fires while teardown is in progress. + // The delay must be long enough it will run after the unregisterNetworkCallback has torn + // down the network and started the teardown timer, and short enough that the lambda is + // scheduled to run before the teardown timer. + final Handler h = new Handler(mCsHandlerThread.getLooper()); + h.postDelayed(duringTeardown, 150); + + // Disconnect the network and check that events happened in the right order. + mCm.unregisterNetworkCallback(callback); + assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertEquals("onNetworkUnwanted", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertEquals("timePasses", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertEquals("onNetworkDisconnected", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + + mCm.unregisterNetworkCallback(callback); + } + + @Test + public void testExplicitlySelected() throws Exception { + NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .build(); + TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + // Bring up validated cell. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + // Bring up unvalidated wifi with explicitlySelected=true. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true, false); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + // Cell Remains the default. + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Lower wifi's score to below than cell, and check that it doesn't disconnect because + // it's explicitly selected. + mWiFiNetworkAgent.adjustScore(-40); + mWiFiNetworkAgent.adjustScore(40); + callback.assertNoCallback(); + + // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to + // wifi even though it's unvalidated. + mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Disconnect wifi, and then reconnect, again with explicitlySelected=true. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true, false); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the + // network to disconnect. + mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // Reconnect, again with explicitlySelected=true, but this time validate. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true, false); + mWiFiNetworkAgent.connect(true); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // BUG: the network will no longer linger, even though it's validated and outscored. + // TODO: fix this. + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + mEthernetNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); + assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + callback.assertNoCallback(); + + // Disconnect wifi, and then reconnect as if the user had selected "yes, don't ask again" + // (i.e., with explicitlySelected=true and acceptUnvalidated=true). Expect to switch to + // wifi immediately. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true, true); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mEthernetNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + mEthernetNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + + // Disconnect and reconnect with explicitlySelected=false and acceptUnvalidated=true. + // Check that the network is not scored specially and that the device prefers cell data. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(false, true); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Clean up. + mWiFiNetworkAgent.disconnect(); + mCellNetworkAgent.disconnect(); + + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + } + + private void tryNetworkFactoryRequests(int capability) throws Exception { + // Verify NOT_RESTRICTED is set appropriately + final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability) + .build().networkCapabilities; + if (capability == NET_CAPABILITY_CBS || capability == NET_CAPABILITY_DUN + || capability == NET_CAPABILITY_EIMS || capability == NET_CAPABILITY_FOTA + || capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS + || capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP + || capability == NET_CAPABILITY_VSIM || capability == NET_CAPABILITY_BIP + || capability == NET_CAPABILITY_ENTERPRISE) { + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + } else { + assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + } + + NetworkCapabilities filter = new NetworkCapabilities(); + filter.addCapability(capability); + // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add + // NOT_VCN_MANAGED automatically but not for NetworkCapabilities, + // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details. + filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); + handlerThread.start(); + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter, mCsHandlerThread); + testFactory.setScoreFilter(40); + ConditionVariable cv = testFactory.getNetworkStartedCV(); + testFactory.register(); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(1); + int expectedRequestCount = 1; + NetworkCallback networkCallback = null; + // For non-INTERNET capabilities we cannot rely on the default request being present, so + // add one. + if (capability != NET_CAPABILITY_INTERNET) { + assertFalse(testFactory.getMyStartRequested()); + NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build(); + networkCallback = new NetworkCallback(); + mCm.requestNetwork(request, networkCallback); + expectedRequestCount++; + testFactory.expectRequestAdd(); + } + waitFor(cv); + testFactory.assertRequestCountEquals(expectedRequestCount); + assertTrue(testFactory.getMyStartRequested()); + + // Now bring in a higher scored network. + TestNetworkAgentWrapper testAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + // Rather than create a validated network which complicates things by registering it's + // own NetworkRequest during startup, just bump up the score to cancel out the + // unvalidated penalty. + testAgent.adjustScore(40); + cv = testFactory.getNetworkStoppedCV(); + + // When testAgent connects, ConnectivityService will re-send us all current requests with + // the new score. There are expectedRequestCount such requests, and we must wait for all of + // them. + testAgent.connect(false); + testAgent.addCapability(capability); + waitFor(cv); + testFactory.expectRequestAdds(expectedRequestCount); + testFactory.assertRequestCountEquals(expectedRequestCount); + assertFalse(testFactory.getMyStartRequested()); + + // Bring in a bunch of requests. + assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); + ConnectivityManager.NetworkCallback[] networkCallbacks = + new ConnectivityManager.NetworkCallback[10]; + for (int i = 0; i< networkCallbacks.length; i++) { + networkCallbacks[i] = new ConnectivityManager.NetworkCallback(); + NetworkRequest.Builder builder = new NetworkRequest.Builder(); + builder.addCapability(capability); + mCm.requestNetwork(builder.build(), networkCallbacks[i]); + } + testFactory.expectRequestAdds(10); + testFactory.assertRequestCountEquals(10 + expectedRequestCount); + assertFalse(testFactory.getMyStartRequested()); + + // Remove the requests. + for (int i = 0; i < networkCallbacks.length; i++) { + mCm.unregisterNetworkCallback(networkCallbacks[i]); + } + testFactory.expectRequestRemoves(10); + testFactory.assertRequestCountEquals(expectedRequestCount); + assertFalse(testFactory.getMyStartRequested()); + + // Drop the higher scored network. + cv = testFactory.getNetworkStartedCV(); + testAgent.disconnect(); + waitFor(cv); + testFactory.expectRequestAdds(expectedRequestCount); + testFactory.assertRequestCountEquals(expectedRequestCount); + assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); + assertTrue(testFactory.getMyStartRequested()); + + testFactory.terminate(); + if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback); + handlerThread.quit(); + } + + @Test + public void testNetworkFactoryRequests() throws Exception { + tryNetworkFactoryRequests(NET_CAPABILITY_MMS); + tryNetworkFactoryRequests(NET_CAPABILITY_SUPL); + tryNetworkFactoryRequests(NET_CAPABILITY_DUN); + tryNetworkFactoryRequests(NET_CAPABILITY_FOTA); + tryNetworkFactoryRequests(NET_CAPABILITY_IMS); + tryNetworkFactoryRequests(NET_CAPABILITY_CBS); + tryNetworkFactoryRequests(NET_CAPABILITY_WIFI_P2P); + tryNetworkFactoryRequests(NET_CAPABILITY_IA); + tryNetworkFactoryRequests(NET_CAPABILITY_RCS); + tryNetworkFactoryRequests(NET_CAPABILITY_XCAP); + tryNetworkFactoryRequests(NET_CAPABILITY_ENTERPRISE); + tryNetworkFactoryRequests(NET_CAPABILITY_EIMS); + tryNetworkFactoryRequests(NET_CAPABILITY_NOT_METERED); + tryNetworkFactoryRequests(NET_CAPABILITY_INTERNET); + tryNetworkFactoryRequests(NET_CAPABILITY_TRUSTED); + tryNetworkFactoryRequests(NET_CAPABILITY_NOT_VPN); + tryNetworkFactoryRequests(NET_CAPABILITY_VSIM); + tryNetworkFactoryRequests(NET_CAPABILITY_BIP); + // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed. + } + + @Test + public void testNetworkFactoryUnregister() throws Exception { + final NetworkCapabilities filter = new NetworkCapabilities(); + filter.clearAll(); + + final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); + handlerThread.start(); + + // Checks that calling setScoreFilter on a NetworkFactory immediately before closing it + // does not crash. + for (int i = 0; i < 100; i++) { + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter, mCsHandlerThread); + // Register the factory and don't be surprised when the default request arrives. + testFactory.register(); + testFactory.expectRequestAdd(); + + testFactory.setScoreFilter(42); + testFactory.terminate(); + + if (i % 2 == 0) { + try { + testFactory.register(); + fail("Re-registering terminated NetworkFactory should throw"); + } catch (IllegalStateException expected) { + } + } + } + handlerThread.quit(); + } + + @Test + public void testNoMutableNetworkRequests() throws Exception { + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE); + NetworkRequest request1 = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_VALIDATED) + .build(); + NetworkRequest request2 = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL) + .build(); + + Class expected = IllegalArgumentException.class; + assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback())); + assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent)); + assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback())); + assertThrows(expected, () -> mCm.requestNetwork(request2, pendingIntent)); + } + + @Test + public void testMMSonWiFi() throws Exception { + // Test bringing up cellular without MMS NetworkRequest gets reaped + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); + mCellNetworkAgent.connectWithoutInternet(); + mCellNetworkAgent.expectDisconnected(); + waitForIdle(); + assertEmpty(mCm.getAllNetworks()); + verifyNoNetwork(); + + // Test bringing up validated WiFi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); + mWiFiNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + + // Register MMS NetworkRequest + NetworkRequest.Builder builder = new NetworkRequest.Builder(); + builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.requestNetwork(builder.build(), networkCallback); + + // Test bringing up unvalidated cellular with MMS + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); + mCellNetworkAgent.connectWithoutInternet(); + networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + verifyActiveNetwork(TRANSPORT_WIFI); + + // Test releasing NetworkRequest disconnects cellular with MMS + mCm.unregisterNetworkCallback(networkCallback); + mCellNetworkAgent.expectDisconnected(); + verifyActiveNetwork(TRANSPORT_WIFI); + } + + @Test + public void testMMSonCell() throws Exception { + // Test bringing up cellular without MMS + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); + mCellNetworkAgent.connect(false); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + + // Register MMS NetworkRequest + NetworkRequest.Builder builder = new NetworkRequest.Builder(); + builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.requestNetwork(builder.build(), networkCallback); + + // Test bringing up MMS cellular network + TestNetworkAgentWrapper + mmsNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); + mmsNetworkAgent.connectWithoutInternet(); + networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent); + verifyActiveNetwork(TRANSPORT_CELLULAR); + + // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent + mCm.unregisterNetworkCallback(networkCallback); + mmsNetworkAgent.expectDisconnected(); + verifyActiveNetwork(TRANSPORT_CELLULAR); + } + + @Test + public void testPartialConnectivity() throws Exception { + // Register network callback. + NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .build(); + TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + // Bring up validated mobile data. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + // Bring up wifi with partial connectivity. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connectWithPartialConnectivity(); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); + + // Mobile data should be the default network. + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + callback.assertNoCallback(); + + // With HTTPS probe disabled, NetworkMonitor should pass the network validation with http + // probe. + mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */); + // If the user chooses yes to use this partial connectivity wifi, switch the default + // network to wifi and check if wifi becomes valid or not. + mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */, + false /* always */); + // If user accepts partial connectivity network, + // NetworkMonitor#setAcceptPartialConnectivity() should be called too. + waitForIdle(); + verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); + + // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is + // validated. + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, + mWiFiNetworkAgent); + assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // Disconnect and reconnect wifi with partial connectivity again. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connectWithPartialConnectivity(); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); + + // Mobile data should be the default network. + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + + // If the user chooses no, disconnect wifi immediately. + mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */, + false /* always */); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // If user accepted partial connectivity before, and device reconnects to that network + // again, but now the network has full connectivity. The network shouldn't contain + // NET_CAPABILITY_PARTIAL_CONNECTIVITY. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + // acceptUnvalidated is also used as setting for accepting partial networks. + mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */, + true /* acceptUnvalidated */); + mWiFiNetworkAgent.connect(true); + + // If user accepted partial connectivity network before, + // NetworkMonitor#setAcceptPartialConnectivity() will be called in + // ConnectivityService#updateNetworkInfo(). + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)); + + // Wifi should be the default network. + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // The user accepted partial connectivity and selected "don't ask again". Now the user + // reconnects to the partial connectivity network. Switch to wifi as soon as partial + // connectivity is detected. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */, + true /* acceptUnvalidated */); + mWiFiNetworkAgent.connectWithPartialConnectivity(); + // If user accepted partial connectivity network before, + // NetworkMonitor#setAcceptPartialConnectivity() will be called in + // ConnectivityService#updateNetworkInfo(). + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); + mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */); + + // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is + // validated. + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // If the user accepted partial connectivity, and the device auto-reconnects to the partial + // connectivity network, it should contain both PARTIAL_CONNECTIVITY and VALIDATED. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.explicitlySelected(false /* explicitlySelected */, + true /* acceptUnvalidated */); + + // NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as + // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls + // notifyNetworkConnected. + mWiFiNetworkAgent.connectWithPartialValidConnectivity(false /* isStrictMode */); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith( + NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + } + + @Test + public void testCaptivePortalOnPartialConnectivity() throws Exception { + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + final TestNetworkCallback validatedCallback = new TestNetworkCallback(); + final NetworkRequest validatedRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_VALIDATED).build(); + mCm.registerNetworkCallback(validatedRequest, validatedCallback); + + // Bring up a network with a captive portal. + // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + String redirectUrl = "http://android.com/path"; + mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl); + + // Check that startCaptivePortalApp sends the expected command to NetworkMonitor. + mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork()); + verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1)) + .launchCaptivePortalApp(); + + // Report that the captive portal is dismissed with partial connectivity, and check that + // callbacks are fired. + mWiFiNetworkAgent.setNetworkPartial(); + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); + waitForIdle(); + captivePortalCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, + mWiFiNetworkAgent); + + // Report partial connectivity is accepted. + mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */); + mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */, + false /* always */); + waitForIdle(); + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); + captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + NetworkCapabilities nc = + validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, + mWiFiNetworkAgent); + + mCm.unregisterNetworkCallback(captivePortalCallback); + mCm.unregisterNetworkCallback(validatedCallback); + } + + @Test + public void testCaptivePortal() throws Exception { + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + final TestNetworkCallback validatedCallback = new TestNetworkCallback(); + final NetworkRequest validatedRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_VALIDATED).build(); + mCm.registerNetworkCallback(validatedRequest, validatedCallback); + + // Bring up a network with a captive portal. + // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + String firstRedirectUrl = "http://example.com/firstPath"; + mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl); + + // Take down network. + // Expect onLost callback. + mWiFiNetworkAgent.disconnect(); + captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // Bring up a network with a captive portal. + // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + String secondRedirectUrl = "http://example.com/secondPath"; + mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl, false /* isStrictMode */); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl); + + // Make captive portal disappear then revalidate. + // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL. + mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */); + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); + captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // Expect NET_CAPABILITY_VALIDATED onAvailable callback. + validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + + // Break network connectivity. + // Expect NET_CAPABILITY_VALIDATED onLost callback. + mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); + mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); + validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + } + + @Test + public void testCaptivePortalApp() throws Exception { + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + final TestNetworkCallback validatedCallback = new TestNetworkCallback(); + final NetworkRequest validatedRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_VALIDATED).build(); + mCm.registerNetworkCallback(validatedRequest, validatedCallback); + + // Bring up wifi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); + + // Check that calling startCaptivePortalApp does nothing. + final int fastTimeoutMs = 100; + mCm.startCaptivePortalApp(wifiNetwork); + waitForIdle(); + verify(mWiFiNetworkAgent.mNetworkMonitor, never()).launchCaptivePortalApp(); + mServiceContext.expectNoStartActivityIntent(fastTimeoutMs); + + // Turn into a captive portal. + mWiFiNetworkAgent.setNetworkPortal("http://example.com", false /* isStrictMode */); + mCm.reportNetworkConnectivity(wifiNetwork, false); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // Check that startCaptivePortalApp sends the expected command to NetworkMonitor. + mCm.startCaptivePortalApp(wifiNetwork); + waitForIdle(); + verify(mWiFiNetworkAgent.mNetworkMonitor).launchCaptivePortalApp(); + + // NetworkMonitor uses startCaptivePortal(Network, Bundle) (startCaptivePortalAppInternal) + final Bundle testBundle = new Bundle(); + final String testKey = "testkey"; + final String testValue = "testvalue"; + testBundle.putString(testKey, testValue); + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_GRANTED); + mCm.startCaptivePortalApp(wifiNetwork, testBundle); + final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS); + assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction()); + assertEquals(testValue, signInIntent.getStringExtra(testKey)); + + // Report that the captive portal is dismissed, and check that callbacks are fired + mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */); + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + mCm.unregisterNetworkCallback(validatedCallback); + mCm.unregisterNetworkCallback(captivePortalCallback); + } + + @Test + public void testAvoidOrIgnoreCaptivePortals() throws Exception { + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + final TestNetworkCallback validatedCallback = new TestNetworkCallback(); + final NetworkRequest validatedRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_VALIDATED).build(); + mCm.registerNetworkCallback(validatedRequest, validatedCallback); + + setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID); + // Bring up a network with a captive portal. + // Expect it to fail to connect and not result in any callbacks. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + String firstRedirectUrl = "http://example.com/firstPath"; + + mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */); + mWiFiNetworkAgent.expectDisconnected(); + mWiFiNetworkAgent.expectPreventReconnectReceived(); + + assertNoCallbacks(captivePortalCallback, validatedCallback); + } + + @Test + public void testCaptivePortalApi() throws Exception { + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final String redirectUrl = "http://example.com/firstPath"; + + mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + final CaptivePortalData testData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(redirectUrl)) + .setBytesRemaining(12345L) + .build(); + + mWiFiNetworkAgent.notifyCapportApiDataChanged(testData); + + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> testData.equals(lp.getCaptivePortalData())); + + final LinkProperties newLps = new LinkProperties(); + newLps.setMtu(1234); + mWiFiNetworkAgent.sendLinkProperties(newLps); + // CaptivePortalData is not lost and unchanged when LPs are received from the NetworkAgent + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> testData.equals(lp.getCaptivePortalData()) && lp.getMtu() == 1234); + } + + private TestNetworkCallback setupNetworkCallbackAndConnectToWifi() throws Exception { + // Grant NETWORK_SETTINGS permission to be able to receive LinkProperties change callbacks + // with sensitive (captive portal) data + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); + final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); + mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + + mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false /* isStrictMode */); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + return captivePortalCallback; + } + + private class CaptivePortalTestData { + CaptivePortalTestData(CaptivePortalData naPasspointData, CaptivePortalData capportData, + CaptivePortalData naOtherData, CaptivePortalData expectedMergedPasspointData, + CaptivePortalData expectedMergedOtherData) { + mNaPasspointData = naPasspointData; + mCapportData = capportData; + mNaOtherData = naOtherData; + mExpectedMergedPasspointData = expectedMergedPasspointData; + mExpectedMergedOtherData = expectedMergedOtherData; + } + + public final CaptivePortalData mNaPasspointData; + public final CaptivePortalData mCapportData; + public final CaptivePortalData mNaOtherData; + public final CaptivePortalData mExpectedMergedPasspointData; + public final CaptivePortalData mExpectedMergedOtherData; + + } + + private CaptivePortalTestData setupCaptivePortalData() { + final CaptivePortalData capportData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT)) + .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT)) + .setExpiryTime(1000000L) + .setBytesRemaining(12345L) + .build(); + + final CaptivePortalData naPasspointData = new CaptivePortalData.Builder() + .setBytesRemaining(80802L) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); + + final CaptivePortalData naOtherData = new CaptivePortalData.Builder() + .setBytesRemaining(80802L) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_OTHER), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) + .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) + .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); + + final CaptivePortalData expectedMergedPasspointData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) + .setBytesRemaining(12345L) + .setExpiryTime(1000000L) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); + + final CaptivePortalData expectedMergedOtherData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) + .setBytesRemaining(12345L) + .setExpiryTime(1000000L) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT)) + .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT)) + .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); + return new CaptivePortalTestData(naPasspointData, capportData, naOtherData, + expectedMergedPasspointData, expectedMergedOtherData); + } + + @Test + public void testMergeCaptivePortalApiWithFriendlyNameAndVenueUrl() throws Exception { + final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); + final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); + + // Baseline capport data + mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); + + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())); + + // Venue URL, T&C URL and friendly name from Network agent with Passpoint source, confirm + // that API data gets precedence on the bytes remaining. + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the capport data is merged + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mExpectedMergedPasspointData + .equals(lp.getCaptivePortalData())); + + // Now send this information from non-Passpoint source, confirm that Capport data takes + // precedence + linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the capport data is merged + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mExpectedMergedOtherData + .equals(lp.getCaptivePortalData())); + + // Create a new LP with no Network agent capport data + final LinkProperties newLps = new LinkProperties(); + newLps.setMtu(1234); + mWiFiNetworkAgent.sendLinkProperties(newLps); + // CaptivePortalData is not lost and has the original values when LPs are received from the + // NetworkAgent + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()) + && lp.getMtu() == 1234); + + // Now send capport data only from the Network agent + mWiFiNetworkAgent.notifyCapportApiDataChanged(null); + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> lp.getCaptivePortalData() == null); + + newLps.setCaptivePortalData(captivePortalTestData.mNaPasspointData); + mWiFiNetworkAgent.sendLinkProperties(newLps); + + // Make sure that only the network agent capport data is available + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData())); + } + + @Test + public void testMergeCaptivePortalDataFromNetworkAgentFirstThenCapport() throws Exception { + final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); + final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); + + // Venue URL and friendly name from Network agent, confirm that API data gets precedence + // on the bytes remaining. + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the data is saved correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData())); + + // Expected merged data: Network agent data is preferred, and values that are not used by + // it are merged from capport data + mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); + + // Make sure that the Capport data is merged correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mExpectedMergedPasspointData.equals( + lp.getCaptivePortalData())); + + // Now set the naData to null + linkProperties.setCaptivePortalData(null); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the Capport data is retained correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())); + } + + @Test + public void testMergeCaptivePortalDataFromNetworkAgentOtherSourceFirstThenCapport() + throws Exception { + final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); + final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); + + // Venue URL and friendly name from Network agent, confirm that API data gets precedence + // on the bytes remaining. + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the data is saved correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mNaOtherData.equals(lp.getCaptivePortalData())); + + // Expected merged data: Network agent data is preferred, and values that are not used by + // it are merged from capport data + mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); + + // Make sure that the Capport data is merged correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mExpectedMergedOtherData.equals( + lp.getCaptivePortalData())); + } + + private NetworkRequest.Builder newWifiRequestBuilder() { + return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI); + } + + /** + * Verify request matching behavior with network specifiers. + * + * This test does not check updating the specifier on a live network because the specifier is + * immutable and this triggers a WTF in + * {@link ConnectivityService#mixInCapabilities(NetworkAgentInfo, NetworkCapabilities)}. + */ + @Test + public void testNetworkSpecifier() throws Exception { + // A NetworkSpecifier subclass that matches all networks but must not be visible to apps. + class ConfidentialMatchAllNetworkSpecifier extends NetworkSpecifier implements + Parcelable { + @Override + public boolean canBeSatisfiedBy(NetworkSpecifier other) { + return true; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) {} + + @Override + public NetworkSpecifier redact() { + return null; + } + } + + // A network specifier that matches either another LocalNetworkSpecifier with the same + // string or a ConfidentialMatchAllNetworkSpecifier, and can be passed to apps as is. + class LocalStringNetworkSpecifier extends NetworkSpecifier implements Parcelable { + private String mString; + + LocalStringNetworkSpecifier(String string) { + mString = string; + } + + @Override + public boolean canBeSatisfiedBy(NetworkSpecifier other) { + if (other instanceof LocalStringNetworkSpecifier) { + return TextUtils.equals(mString, + ((LocalStringNetworkSpecifier) other).mString); + } + if (other instanceof ConfidentialMatchAllNetworkSpecifier) return true; + return false; + } + + @Override + public int describeContents() { + return 0; + } + @Override + public void writeToParcel(Parcel dest, int flags) {} + } + + + NetworkRequest rEmpty1 = newWifiRequestBuilder().build(); + NetworkRequest rEmpty2 = newWifiRequestBuilder().setNetworkSpecifier((String) null).build(); + NetworkRequest rEmpty3 = newWifiRequestBuilder().setNetworkSpecifier("").build(); + NetworkRequest rEmpty4 = newWifiRequestBuilder().setNetworkSpecifier( + (NetworkSpecifier) null).build(); + NetworkRequest rFoo = newWifiRequestBuilder().setNetworkSpecifier( + new LocalStringNetworkSpecifier("foo")).build(); + NetworkRequest rBar = newWifiRequestBuilder().setNetworkSpecifier( + new LocalStringNetworkSpecifier("bar")).build(); + + TestNetworkCallback cEmpty1 = new TestNetworkCallback(); + TestNetworkCallback cEmpty2 = new TestNetworkCallback(); + TestNetworkCallback cEmpty3 = new TestNetworkCallback(); + TestNetworkCallback cEmpty4 = new TestNetworkCallback(); + TestNetworkCallback cFoo = new TestNetworkCallback(); + TestNetworkCallback cBar = new TestNetworkCallback(); + TestNetworkCallback[] emptyCallbacks = new TestNetworkCallback[] { + cEmpty1, cEmpty2, cEmpty3, cEmpty4 }; + + mCm.registerNetworkCallback(rEmpty1, cEmpty1); + mCm.registerNetworkCallback(rEmpty2, cEmpty2); + mCm.registerNetworkCallback(rEmpty3, cEmpty3); + mCm.registerNetworkCallback(rEmpty4, cEmpty4); + mCm.registerNetworkCallback(rFoo, cFoo); + mCm.registerNetworkCallback(rBar, cBar); + + LocalStringNetworkSpecifier nsFoo = new LocalStringNetworkSpecifier("foo"); + LocalStringNetworkSpecifier nsBar = new LocalStringNetworkSpecifier("bar"); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, null /* specifier */, + cEmpty1, cEmpty2, cEmpty3, cEmpty4); + assertNoCallbacks(cFoo, cBar); + + mWiFiNetworkAgent.disconnect(); + expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.setNetworkSpecifier(nsFoo); + mWiFiNetworkAgent.connect(false); + expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, nsFoo, + cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo); + cBar.assertNoCallback(); + assertEquals(nsFoo, + mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); + assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo); + + mWiFiNetworkAgent.disconnect(); + expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.setNetworkSpecifier(nsBar); + mWiFiNetworkAgent.connect(false); + expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, nsBar, + cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar); + cFoo.assertNoCallback(); + assertEquals(nsBar, + mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); + + mWiFiNetworkAgent.disconnect(); + expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar); + cFoo.assertNoCallback(); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier()); + mWiFiNetworkAgent.connect(false); + expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, null /* specifier */, + cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar); + assertNull( + mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); + + mWiFiNetworkAgent.disconnect(); + expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar); + } + + /** + * @return the context's attribution tag + */ + private String getAttributionTag() { + return null; + } + + @Test + public void testInvalidNetworkSpecifier() { + assertThrows(IllegalArgumentException.class, () -> { + NetworkRequest.Builder builder = new NetworkRequest.Builder(); + builder.setNetworkSpecifier(new MatchAllNetworkSpecifier()); + }); + + assertThrows(IllegalArgumentException.class, () -> { + NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addTransportType(TRANSPORT_WIFI) + .setNetworkSpecifier(new MatchAllNetworkSpecifier()); + mService.requestNetwork(Process.INVALID_UID, networkCapabilities, + NetworkRequest.Type.REQUEST.ordinal(), null, 0, null, + ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE, + mContext.getPackageName(), getAttributionTag()); + }); + + class NonParcelableSpecifier extends NetworkSpecifier { + @Override + public boolean canBeSatisfiedBy(NetworkSpecifier other) { + return false; + } + }; + class ParcelableSpecifier extends NonParcelableSpecifier implements Parcelable { + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel p, int flags) {} + } + + final NetworkRequest.Builder builder = + new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET); + assertThrows(ClassCastException.class, () -> { + builder.setNetworkSpecifier(new NonParcelableSpecifier()); + Parcel parcelW = Parcel.obtain(); + builder.build().writeToParcel(parcelW, 0); + }); + + final NetworkRequest nr = + new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET) + .setNetworkSpecifier(new ParcelableSpecifier()) + .build(); + assertNotNull(nr); + + assertThrows(BadParcelableException.class, () -> { + Parcel parcelW = Parcel.obtain(); + nr.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + NetworkRequest rereadNr = NetworkRequest.CREATOR.createFromParcel(parcelR); + }); + } + + @Test + public void testNetworkRequestUidSpoofSecurityException() throws Exception { + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + NetworkRequest networkRequest = newWifiRequestBuilder().build(); + TestNetworkCallback networkCallback = new TestNetworkCallback(); + doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString()); + assertThrows(SecurityException.class, () -> { + mCm.requestNetwork(networkRequest, networkCallback); + }); + } + + @Test + public void testInvalidSignalStrength() { + NetworkRequest r = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .addTransportType(TRANSPORT_WIFI) + .setSignalStrength(-75) + .build(); + // Registering a NetworkCallback with signal strength but w/o NETWORK_SIGNAL_STRENGTH_WAKEUP + // permission should get SecurityException. + assertThrows(SecurityException.class, () -> + mCm.registerNetworkCallback(r, new NetworkCallback())); + + assertThrows(SecurityException.class, () -> + mCm.registerNetworkCallback(r, PendingIntent.getService( + mServiceContext, 0 /* requestCode */, new Intent(), FLAG_IMMUTABLE))); + + // Requesting a Network with signal strength should get IllegalArgumentException. + assertThrows(IllegalArgumentException.class, () -> + mCm.requestNetwork(r, new NetworkCallback())); + + assertThrows(IllegalArgumentException.class, () -> + mCm.requestNetwork(r, PendingIntent.getService( + mServiceContext, 0 /* requestCode */, new Intent(), FLAG_IMMUTABLE))); + } + + @Test + public void testRegisterDefaultNetworkCallback() throws Exception { + // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback. + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultNetworkCallback); + defaultNetworkCallback.assertNoCallback(); + + final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, handler); + systemDefaultCallback.assertNoCallback(); + + // Create a TRANSPORT_CELLULAR request to keep the mobile interface up + // whenever Wi-Fi is up. Without this, the mobile network agent is + // reaped before any other activity can take place. + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + cellNetworkCallback.assertNoCallback(); + + // Bring up cell and expect CALLBACK_AVAILABLE. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Bring up wifi and expect CALLBACK_AVAILABLE. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + cellNetworkCallback.assertNoCallback(); + defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Bring down cell. Expect no default network callback, since it wasn't the default. + mCellNetworkAgent.disconnect(); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultNetworkCallback.assertNoCallback(); + systemDefaultCallback.assertNoCallback(); + assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Bring up cell. Expect no default network callback, since it won't be the default. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultNetworkCallback.assertNoCallback(); + systemDefaultCallback.assertNoCallback(); + assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + // Bring down wifi. Expect the default network callback to notified of LOST wifi + // followed by AVAILABLE cell. + mWiFiNetworkAgent.disconnect(); + cellNetworkCallback.assertNoCallback(); + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + mCellNetworkAgent.disconnect(); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + waitForIdle(); + assertEquals(null, mCm.getActiveNetwork()); + + mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); + assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(null, systemDefaultCallback.getLastAvailableNetwork()); + + mMockVpn.disconnect(); + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + systemDefaultCallback.assertNoCallback(); + waitForIdle(); + assertEquals(null, mCm.getActiveNetwork()); + } + + @Test + public void testAdditionalStateCallbacks() throws Exception { + // File a network request for mobile. + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + + // Bring up the mobile network. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + // We should get onAvailable(), onCapabilitiesChanged(), and + // onLinkPropertiesChanged() in rapid succession. Additionally, we + // should get onCapabilitiesChanged() when the mobile network validates. + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + + // Update LinkProperties. + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("foonet_data0"); + mCellNetworkAgent.sendLinkProperties(lp); + // We should get onLinkPropertiesChanged(). + cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + + // Suspend the network. + mCellNetworkAgent.suspend(); + cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_SUSPENDED, + mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertEquals(NetworkInfo.State.SUSPENDED, mCm.getActiveNetworkInfo().getState()); + + // Register a garden variety default network request. + TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(dfltNetworkCallback); + // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(), + // as well as onNetworkSuspended() in rapid succession. + dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true); + dfltNetworkCallback.assertNoCallback(); + mCm.unregisterNetworkCallback(dfltNetworkCallback); + + mCellNetworkAgent.resume(); + cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_SUSPENDED, + mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.RESUMED, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertEquals(NetworkInfo.State.CONNECTED, mCm.getActiveNetworkInfo().getState()); + + dfltNetworkCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(dfltNetworkCallback); + // This time onNetworkSuspended should not be called. + dfltNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + dfltNetworkCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(dfltNetworkCallback); + mCm.unregisterNetworkCallback(cellNetworkCallback); + } + + @Test + public void testRegisterPrivilegedDefaultCallbacksRequireNetworkSettings() throws Exception { + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false /* validated */); + + final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + final TestNetworkCallback callback = new TestNetworkCallback(); + assertThrows(SecurityException.class, + () -> mCm.registerSystemDefaultNetworkCallback(callback, handler)); + callback.assertNoCallback(); + assertThrows(SecurityException.class, + () -> mCm.registerDefaultNetworkCallbackForUid(APP1_UID, callback, handler)); + callback.assertNoCallback(); + + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + mCm.registerSystemDefaultNetworkCallback(callback, handler); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); + + mCm.registerDefaultNetworkCallbackForUid(APP1_UID, callback, handler); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); + } + + private void setCaptivePortalMode(int mode) { + ContentResolver cr = mServiceContext.getContentResolver(); + Settings.Global.putInt(cr, ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE, mode); + } + + private void setAlwaysOnNetworks(boolean enable) { + ContentResolver cr = mServiceContext.getContentResolver(); + Settings.Global.putInt(cr, ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON, + enable ? 1 : 0); + mService.updateAlwaysOnNetworks(); + waitForIdle(); + } + + private void setPrivateDnsSettings(int mode, String specifier) { + ConnectivitySettingsManager.setPrivateDnsMode(mServiceContext, mode); + ConnectivitySettingsManager.setPrivateDnsHostname(mServiceContext, specifier); + mService.updatePrivateDnsSettings(); + waitForIdle(); + } + + private boolean isForegroundNetwork(TestNetworkAgentWrapper network) { + NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork()); + assertNotNull(nc); + return nc.hasCapability(NET_CAPABILITY_FOREGROUND); + } + + @Test + public void testBackgroundNetworks() throws Exception { + // Create a cellular background request. + grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); + final TestNetworkCallback cellBgCallback = new TestNetworkCallback(); + mCm.requestBackgroundNetwork(new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(), + cellBgCallback, mCsHandlerThread.getThreadHandler()); + + // Make callbacks for monitoring. + final NetworkRequest request = new NetworkRequest.Builder().build(); + final NetworkRequest fgRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_FOREGROUND).build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + final TestNetworkCallback fgCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + mCm.registerNetworkCallback(fgRequest, fgCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + assertTrue(isForegroundNetwork(mCellNetworkAgent)); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + + // When wifi connects, cell lingers. + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + fgCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + assertTrue(isForegroundNetwork(mCellNetworkAgent)); + assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); + + // When lingering is complete, cell is still there but is now in the background. + waitForIdle(); + int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4; + fgCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, timeoutMs); + // Expect a network capabilities update sans FOREGROUND. + callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); + assertFalse(isForegroundNetwork(mCellNetworkAgent)); + assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); + + // File a cell request and check that cell comes into the foreground. + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + final TestNetworkCallback cellCallback = new TestNetworkCallback(); + mCm.requestNetwork(cellRequest, cellCallback); + cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + // Expect a network capabilities update with FOREGROUND, because the most recent + // request causes its state to change. + cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); + callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); + assertTrue(isForegroundNetwork(mCellNetworkAgent)); + assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); + + // Release the request. The network immediately goes into the background, since it was not + // lingering. + mCm.unregisterNetworkCallback(cellCallback); + fgCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + // Expect a network capabilities update sans FOREGROUND. + callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); + assertFalse(isForegroundNetwork(mCellNetworkAgent)); + assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); + + // Disconnect wifi and check that cell is foreground again. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + fgCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertTrue(isForegroundNetwork(mCellNetworkAgent)); + + mCm.unregisterNetworkCallback(callback); + mCm.unregisterNetworkCallback(fgCallback); + mCm.unregisterNetworkCallback(cellBgCallback); + } + + @Ignore // This test has instrinsic chances of spurious failures: ignore for continuous testing. + public void benchmarkRequestRegistrationAndCallbackDispatch() throws Exception { + // TODO: turn this unit test into a real benchmarking test. + // Benchmarks connecting and switching performance in the presence of a large number of + // NetworkRequests. + // 1. File NUM_REQUESTS requests. + // 2. Have a network connect. Wait for NUM_REQUESTS onAvailable callbacks to fire. + // 3. Have a new network connect and outscore the previous. Wait for NUM_REQUESTS onLosing + // and NUM_REQUESTS onAvailable callbacks to fire. + // See how long it took. + final int NUM_REQUESTS = 90; + final int REGISTER_TIME_LIMIT_MS = 200; + final int CONNECT_TIME_LIMIT_MS = 60; + final int SWITCH_TIME_LIMIT_MS = 60; + final int UNREGISTER_TIME_LIMIT_MS = 20; + + final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); + final NetworkCallback[] callbacks = new NetworkCallback[NUM_REQUESTS]; + final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS); + final CountDownLatch losingLatch = new CountDownLatch(NUM_REQUESTS); + + for (int i = 0; i < NUM_REQUESTS; i++) { + callbacks[i] = new NetworkCallback() { + @Override public void onAvailable(Network n) { availableLatch.countDown(); } + @Override public void onLosing(Network n, int t) { losingLatch.countDown(); } + }; + } + + assertRunsInAtMost("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> { + for (NetworkCallback cb : callbacks) { + mCm.registerNetworkCallback(request, cb); + } + }); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + // Don't request that the network validate, because otherwise connect() will block until + // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired, + // and we won't actually measure anything. + mCellNetworkAgent.connect(false); + + long onAvailableDispatchingDuration = durationOf(() -> { + await(availableLatch, 10 * CONNECT_TIME_LIMIT_MS); + }); + Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms", + NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS, + onAvailableDispatchingDuration)); + assertTrue(String.format("Dispatching %d onAvailable callbacks in %dms, expected %dms", + NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS), + onAvailableDispatchingDuration <= CONNECT_TIME_LIMIT_MS); + + // Give wifi a high enough score that we'll linger cell when wifi comes up. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.adjustScore(40); + mWiFiNetworkAgent.connect(false); + + long onLostDispatchingDuration = durationOf(() -> { + await(losingLatch, 10 * SWITCH_TIME_LIMIT_MS); + }); + Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms", + NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration)); + assertTrue(String.format("Dispatching %d onLosing callbacks in %dms, expected %dms", + NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS), + onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS); + + assertRunsInAtMost("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> { + for (NetworkCallback cb : callbacks) { + mCm.unregisterNetworkCallback(cb); + } + }); + } + + @Test + public void testMobileDataAlwaysOn() throws Exception { + grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); + + final HandlerThread handlerThread = new HandlerThread("MobileDataAlwaysOnFactory"); + handlerThread.start(); + NetworkCapabilities filter = new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .addCapability(NET_CAPABILITY_INTERNET); + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter, mCsHandlerThread); + testFactory.setScoreFilter(40); + + // Register the factory and expect it to start looking for a network. + testFactory.register(); + + try { + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(1); + assertTrue(testFactory.getMyStartRequested()); + + // Bring up wifi. The factory stops looking for a network. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + // Score 60 - 40 penalty for not validated yet, then 60 when it validates + mWiFiNetworkAgent.connect(true); + // Default request and mobile always on request + testFactory.expectRequestAdds(2); + assertFalse(testFactory.getMyStartRequested()); + + // Turn on mobile data always on. The factory starts looking again. + setAlwaysOnNetworks(true); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(2); + + assertTrue(testFactory.getMyStartRequested()); + + // Bring up cell data and check that the factory stops looking. + assertLength(1, mCm.getAllNetworks()); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + testFactory.expectRequestAdds(2); // Unvalidated and validated + testFactory.assertRequestCountEquals(2); + // The cell network outscores the factory filter, so start is not requested. + assertFalse(testFactory.getMyStartRequested()); + + // Check that cell data stays up. + waitForIdle(); + verifyActiveNetwork(TRANSPORT_WIFI); + assertLength(2, mCm.getAllNetworks()); + + // Turn off mobile data always on and expect the request to disappear... + setAlwaysOnNetworks(false); + testFactory.expectRequestRemove(); + + // ... and cell data to be torn down after nascent network timeout. + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, + mService.mNascentDelayMs + TEST_CALLBACK_TIMEOUT_MS); + waitForIdle(); + assertLength(1, mCm.getAllNetworks()); + } finally { + testFactory.terminate(); + mCm.unregisterNetworkCallback(cellNetworkCallback); + handlerThread.quit(); + } + } + + @Test + public void testAvoidBadWifiSetting() throws Exception { + final ContentResolver cr = mServiceContext.getContentResolver(); + final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI; + + mPolicyTracker.mConfigRestrictsAvoidBadWifi = false; + String[] values = new String[] {null, "0", "1"}; + for (int i = 0; i < values.length; i++) { + Settings.Global.putInt(cr, settingName, 1); + mPolicyTracker.reevaluate(); + waitForIdle(); + String msg = String.format("config=false, setting=%s", values[i]); + assertTrue(mService.avoidBadWifi()); + assertFalse(msg, mPolicyTracker.shouldNotifyWifiUnvalidated()); + } + + mPolicyTracker.mConfigRestrictsAvoidBadWifi = true; + + Settings.Global.putInt(cr, settingName, 0); + mPolicyTracker.reevaluate(); + waitForIdle(); + assertFalse(mService.avoidBadWifi()); + assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated()); + + Settings.Global.putInt(cr, settingName, 1); + mPolicyTracker.reevaluate(); + waitForIdle(); + assertTrue(mService.avoidBadWifi()); + assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated()); + + Settings.Global.putString(cr, settingName, null); + mPolicyTracker.reevaluate(); + waitForIdle(); + assertFalse(mService.avoidBadWifi()); + assertTrue(mPolicyTracker.shouldNotifyWifiUnvalidated()); + } + + @Ignore("Refactoring in progress b/178071397") + @Test + public void testAvoidBadWifi() throws Exception { + final ContentResolver cr = mServiceContext.getContentResolver(); + + // Pretend we're on a carrier that restricts switching away from bad wifi. + mPolicyTracker.mConfigRestrictsAvoidBadWifi = true; + + // File a request for cell to ensure it doesn't go down. + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + + TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + NetworkRequest validatedWifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_VALIDATED) + .build(); + TestNetworkCallback validatedWifiCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback); + + Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 0); + mPolicyTracker.reevaluate(); + + // Bring up validated cell. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + Network cellNetwork = mCellNetworkAgent.getNetwork(); + + // Bring up validated wifi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); + + // Fail validation on wifi. + mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); + mCm.reportNetworkConnectivity(wifiNetwork, false); + defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // Because avoid bad wifi is off, we don't switch to cellular. + defaultCallback.assertNoCallback(); + assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertEquals(mCm.getActiveNetwork(), wifiNetwork); + + // Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect + // that we switch back to cell. + mPolicyTracker.mConfigRestrictsAvoidBadWifi = false; + mPolicyTracker.reevaluate(); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertEquals(mCm.getActiveNetwork(), cellNetwork); + + // Switch back to a restrictive carrier. + mPolicyTracker.mConfigRestrictsAvoidBadWifi = true; + mPolicyTracker.reevaluate(); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mCm.getActiveNetwork(), wifiNetwork); + + // Simulate the user selecting "switch" on the dialog, and check that we switch to cell. + mCm.setAvoidUnvalidated(wifiNetwork); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertEquals(mCm.getActiveNetwork(), cellNetwork); + + // Disconnect and reconnect wifi to clear the one-time switch above. + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + wifiNetwork = mWiFiNetworkAgent.getNetwork(); + + // Fail validation on wifi and expect the dialog to appear. + mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); + mCm.reportNetworkConnectivity(wifiNetwork, false); + defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // Simulate the user selecting "switch" and checking the don't ask again checkbox. + Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1); + mPolicyTracker.reevaluate(); + + // We now switch to cell. + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( + NET_CAPABILITY_VALIDATED)); + assertEquals(mCm.getActiveNetwork(), cellNetwork); + + // Simulate the user turning the cellular fallback setting off and then on. + // We switch to wifi and then to cell. + Settings.Global.putString(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null); + mPolicyTracker.reevaluate(); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mCm.getActiveNetwork(), wifiNetwork); + Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1); + mPolicyTracker.reevaluate(); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertEquals(mCm.getActiveNetwork(), cellNetwork); + + // If cell goes down, we switch to wifi. + mCellNetworkAgent.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + validatedWifiCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(cellNetworkCallback); + mCm.unregisterNetworkCallback(validatedWifiCallback); + mCm.unregisterNetworkCallback(defaultCallback); + } + + @Test + public void testMeteredMultipathPreferenceSetting() throws Exception { + final ContentResolver cr = mServiceContext.getContentResolver(); + final String settingName = ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE; + + for (int config : Arrays.asList(0, 3, 2)) { + for (String setting: Arrays.asList(null, "0", "2", "1")) { + mPolicyTracker.mConfigMeteredMultipathPreference = config; + Settings.Global.putString(cr, settingName, setting); + mPolicyTracker.reevaluate(); + waitForIdle(); + + final int expected = (setting != null) ? Integer.parseInt(setting) : config; + String msg = String.format("config=%d, setting=%s", config, setting); + assertEquals(msg, expected, mCm.getMultipathPreference(null)); + } + } + } + + /** + * Validate that a satisfied network request does not trigger onUnavailable() once the + * time-out period expires. + */ + @Test + public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() throws Exception { + NetworkRequest nr = new NetworkRequest.Builder().addTransportType( + NetworkCapabilities.TRANSPORT_WIFI).build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false, + TEST_CALLBACK_TIMEOUT_MS); + + // pass timeout and validate that UNAVAILABLE is not called + networkCallback.assertNoCallback(); + } + + /** + * Validate that a satisfied network request followed by a disconnected (lost) network does + * not trigger onUnavailable() once the time-out period expires. + */ + @Test + public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() throws Exception { + NetworkRequest nr = new NetworkRequest.Builder().addTransportType( + NetworkCapabilities.TRANSPORT_WIFI).build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false, + TEST_CALLBACK_TIMEOUT_MS); + mWiFiNetworkAgent.disconnect(); + networkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + // Validate that UNAVAILABLE is not called + networkCallback.assertNoCallback(); + } + + /** + * Validate that when a time-out is specified for a network request the onUnavailable() + * callback is called when time-out expires. Then validate that if network request is + * (somehow) satisfied - the callback isn't called later. + */ + @Test + public void testTimedoutNetworkRequest() throws Exception { + NetworkRequest nr = new NetworkRequest.Builder().addTransportType( + NetworkCapabilities.TRANSPORT_WIFI).build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + final int timeoutMs = 10; + mCm.requestNetwork(nr, networkCallback, timeoutMs); + + // pass timeout and validate that UNAVAILABLE is called + networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); + + // create a network satisfying request - validate that request not triggered + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + networkCallback.assertNoCallback(); + } + + /** + * Validate that when a network request is unregistered (cancelled), no posterior event can + * trigger the callback. + */ + @Test + public void testNoCallbackAfterUnregisteredNetworkRequest() throws Exception { + NetworkRequest nr = new NetworkRequest.Builder().addTransportType( + NetworkCapabilities.TRANSPORT_WIFI).build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + final int timeoutMs = 10; + + mCm.requestNetwork(nr, networkCallback, timeoutMs); + mCm.unregisterNetworkCallback(networkCallback); + // Regardless of the timeout, unregistering the callback in ConnectivityManager ensures + // that this callback will not be called. + networkCallback.assertNoCallback(); + + // create a network satisfying request - validate that request not triggered + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + networkCallback.assertNoCallback(); + } + + @Test + public void testUnfulfillableNetworkRequest() throws Exception { + runUnfulfillableNetworkRequest(false); + } + + @Test + public void testUnfulfillableNetworkRequestAfterUnregister() throws Exception { + runUnfulfillableNetworkRequest(true); + } + + /** + * Validate the callback flow for a factory releasing a request as unfulfillable. + */ + private void runUnfulfillableNetworkRequest(boolean preUnregister) throws Exception { + NetworkRequest nr = new NetworkRequest.Builder().addTransportType( + NetworkCapabilities.TRANSPORT_WIFI).build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + + final HandlerThread handlerThread = new HandlerThread("testUnfulfillableNetworkRequest"); + handlerThread.start(); + NetworkCapabilities filter = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_INTERNET); + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter, mCsHandlerThread); + testFactory.setScoreFilter(40); + + // Register the factory and expect it to receive the default request. + testFactory.register(); + testFactory.expectRequestAdd(); + + // Now file the test request and expect it. + mCm.requestNetwork(nr, networkCallback); + final NetworkRequest newRequest = testFactory.expectRequestAdd().request; + + if (preUnregister) { + mCm.unregisterNetworkCallback(networkCallback); + + // Simulate the factory releasing the request as unfulfillable: no-op since + // the callback has already been unregistered (but a test that no exceptions are + // thrown). + testFactory.triggerUnfulfillable(newRequest); + } else { + // Simulate the factory releasing the request as unfulfillable and expect onUnavailable! + testFactory.triggerUnfulfillable(newRequest); + + networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); + + // unregister network callback - a no-op (since already freed by the + // on-unavailable), but should not fail or throw exceptions. + mCm.unregisterNetworkCallback(networkCallback); + } + + testFactory.expectRequestRemove(); + + testFactory.terminate(); + handlerThread.quit(); + } + + private static class TestKeepaliveCallback extends PacketKeepaliveCallback { + + public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR } + + private class CallbackValue { + public CallbackType callbackType; + public int error; + + public CallbackValue(CallbackType type) { + this.callbackType = type; + this.error = PacketKeepalive.SUCCESS; + assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); + } + + public CallbackValue(CallbackType type, int error) { + this.callbackType = type; + this.error = error; + assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); + } + + @Override + public boolean equals(Object o) { + return o instanceof CallbackValue && + this.callbackType == ((CallbackValue) o).callbackType && + this.error == ((CallbackValue) o).error; + } + + @Override + public String toString() { + return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error); + } + } + + private final LinkedBlockingQueue mCallbacks = new LinkedBlockingQueue<>(); + + @Override + public void onStarted() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); + } + + @Override + public void onStopped() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); + } + + @Override + public void onError(int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + private void expectCallback(CallbackValue callbackValue) throws InterruptedException { + assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + public void expectStarted() throws Exception { + expectCallback(new CallbackValue(CallbackType.ON_STARTED)); + } + + public void expectStopped() throws Exception { + expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); + } + + public void expectError(int error) throws Exception { + expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); + } + } + + private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback { + + public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; + + private class CallbackValue { + public CallbackType callbackType; + public int error; + + CallbackValue(CallbackType type) { + this.callbackType = type; + this.error = SocketKeepalive.SUCCESS; + assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); + } + + CallbackValue(CallbackType type, int error) { + this.callbackType = type; + this.error = error; + assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); + } + + @Override + public boolean equals(Object o) { + return o instanceof CallbackValue + && this.callbackType == ((CallbackValue) o).callbackType + && this.error == ((CallbackValue) o).error; + } + + @Override + public String toString() { + return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, + error); + } + } + + private LinkedBlockingQueue mCallbacks = new LinkedBlockingQueue<>(); + private final Executor mExecutor; + + TestSocketKeepaliveCallback(@NonNull Executor executor) { + mExecutor = executor; + } + + @Override + public void onStarted() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); + } + + @Override + public void onStopped() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); + } + + @Override + public void onError(int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + private void expectCallback(CallbackValue callbackValue) throws InterruptedException { + assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + + } + + public void expectStarted() throws InterruptedException { + expectCallback(new CallbackValue(CallbackType.ON_STARTED)); + } + + public void expectStopped() throws InterruptedException { + expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); + } + + public void expectError(int error) throws InterruptedException { + expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + public void assertNoCallback() { + waitForIdleSerialExecutor(mExecutor, TIMEOUT_MS); + CallbackValue cv = mCallbacks.peek(); + assertNull("Unexpected callback: " + cv, cv); + } + } + + private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception { + // Ensure the network is disconnected before anything else occurs + if (mWiFiNetworkAgent != null) { + assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); + } + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); + mWiFiNetworkAgent.connect(true); + b.expectBroadcast(); + verifyActiveNetwork(TRANSPORT_WIFI); + mWiFiNetworkAgent.sendLinkProperties(lp); + waitForIdle(); + return mWiFiNetworkAgent.getNetwork(); + } + + @Test + public void testPacketKeepalives() throws Exception { + InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); + InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); + InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); + + final int validKaInterval = 15; + final int invalidKaInterval = 9; + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + + Network notMyNet = new Network(61234); + Network myNet = connectKeepaliveNetwork(lp); + + TestKeepaliveCallback callback = new TestKeepaliveCallback(); + PacketKeepalive ka; + + // Attempt to start keepalives with invalid parameters and check for errors. + ka = mCm.startNattKeepalive(notMyNet, validKaInterval, callback, myIPv4, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); + + ka = mCm.startNattKeepalive(myNet, invalidKaInterval, callback, myIPv4, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_INTERVAL); + + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 1234, dstIPv6); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv6, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + // NAT-T is only supported for IPv4. + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv6, 1234, dstIPv6); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 123456, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); + + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 123456, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); + + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + // Check that a started keepalive can be stopped. + mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS); + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + mWiFiNetworkAgent.setStopKeepaliveEvent(PacketKeepalive.SUCCESS); + ka.stop(); + callback.expectStopped(); + + // Check that deleting the IP address stops the keepalive. + LinkProperties bogusLp = new LinkProperties(lp); + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); + bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); + mWiFiNetworkAgent.sendLinkProperties(bogusLp); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + mWiFiNetworkAgent.sendLinkProperties(lp); + + // Check that a started keepalive is stopped correctly when the network disconnects. + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent.expectDisconnected(); + callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); + + // ... and that stopping it after that has no adverse effects. + waitForIdle(); + final Network myNetAlias = myNet; + assertNull(mCm.getNetworkCapabilities(myNetAlias)); + ka.stop(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS); + + // Check that keepalive slots start from 1 and increment. The first one gets slot 1. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + + // The second one gets slot 2. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); + TestKeepaliveCallback callback2 = new TestKeepaliveCallback(); + PacketKeepalive ka2 = mCm.startNattKeepalive( + myNet, validKaInterval, callback2, myIPv4, 6789, dstIPv4); + callback2.expectStarted(); + + // Now stop the first one and create a third. This also gets slot 1. + ka.stop(); + callback.expectStopped(); + + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + TestKeepaliveCallback callback3 = new TestKeepaliveCallback(); + PacketKeepalive ka3 = mCm.startNattKeepalive( + myNet, validKaInterval, callback3, myIPv4, 9876, dstIPv4); + callback3.expectStarted(); + + ka2.stop(); + callback2.expectStopped(); + + ka3.stop(); + callback3.expectStopped(); + } + + // Helper method to prepare the executor and run test + private void runTestWithSerialExecutors(ExceptionUtils.ThrowingConsumer functor) + throws Exception { + final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor(); + final Executor executorInline = (Runnable r) -> r.run(); + functor.accept(executorSingleThread); + executorSingleThread.shutdown(); + functor.accept(executorInline); + } + + @Test + public void testNattSocketKeepalives() throws Exception { + runTestWithSerialExecutors(executor -> doTestNattSocketKeepalivesWithExecutor(executor)); + runTestWithSerialExecutors(executor -> doTestNattSocketKeepalivesFdWithExecutor(executor)); + } + + private void doTestNattSocketKeepalivesWithExecutor(Executor executor) throws Exception { + // TODO: 1. Move this outside of ConnectivityServiceTest. + // 2. Make test to verify that Nat-T keepalive socket is created by IpSecService. + // 3. Mock ipsec service. + final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); + final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); + final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); + + final int validKaInterval = 15; + final int invalidKaInterval = 9; + + final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); + final int srcPort = testSocket.getPort(); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + + Network notMyNet = new Network(61234); + Network myNet = connectKeepaliveNetwork(lp); + + TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor); + + // Attempt to start keepalives with invalid parameters and check for errors. + // Invalid network. + try (SocketKeepalive ka = mCm.createSocketKeepalive( + notMyNet, testSocket, myIPv4, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + } + + // Invalid interval. + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { + ka.start(invalidKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL); + } + + // Invalid destination. + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv4, dstIPv6, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + } + + // Invalid source; + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv6, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + } + + // NAT-T is only supported for IPv4. + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv6, dstIPv6, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + } + + // Basic check before testing started keepalive. + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_UNSUPPORTED); + } + + // Check that a started keepalive can be stopped. + mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectStarted(); + mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS); + ka.stop(); + callback.expectStopped(); + + // Check that keepalive could be restarted. + ka.start(validKaInterval); + callback.expectStarted(); + ka.stop(); + callback.expectStopped(); + + // Check that keepalive can be restarted without waiting for callback. + ka.start(validKaInterval); + callback.expectStarted(); + ka.stop(); + ka.start(validKaInterval); + callback.expectStopped(); + callback.expectStarted(); + ka.stop(); + callback.expectStopped(); + } + + // Check that deleting the IP address stops the keepalive. + LinkProperties bogusLp = new LinkProperties(lp); + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectStarted(); + bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); + bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); + mWiFiNetworkAgent.sendLinkProperties(bogusLp); + callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); + mWiFiNetworkAgent.sendLinkProperties(lp); + } + + // Check that a started keepalive is stopped correctly when the network disconnects. + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectStarted(); + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent.expectDisconnected(); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + + // ... and that stopping it after that has no adverse effects. + waitForIdle(); + final Network myNetAlias = myNet; + assertNull(mCm.getNetworkCapabilities(myNetAlias)); + ka.stop(); + callback.assertNoCallback(); + } + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); + + // Check that a stop followed by network disconnects does not result in crash. + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectStarted(); + // Delay the response of keepalive events in networkAgent long enough to make sure + // the follow-up network disconnection will be processed first. + mWiFiNetworkAgent.setKeepaliveResponseDelay(3 * TIMEOUT_MS); + ka.stop(); + + // Make sure the stop has been processed. Wait for executor idle is needed to prevent + // flaky since the actual stop call to the service is delegated to executor thread. + waitForIdleSerialExecutor(executor, TIMEOUT_MS); + waitForIdle(); + + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent.expectDisconnected(); + callback.expectStopped(); + callback.assertNoCallback(); + } + + // Reconnect. + waitForIdle(); + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); + + // Check that keepalive slots start from 1 and increment. The first one gets slot 1. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + int srcPort2 = 0; + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectStarted(); + + // The second one gets slot 2. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); + final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(); + srcPort2 = testSocket2.getPort(); + TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor); + try (SocketKeepalive ka2 = mCm.createSocketKeepalive( + myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) { + ka2.start(validKaInterval); + callback2.expectStarted(); + + ka.stop(); + callback.expectStopped(); + + ka2.stop(); + callback2.expectStopped(); + + testSocket.close(); + testSocket2.close(); + } + } + + // Check that there is no port leaked after all keepalives and sockets are closed. + // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7. + // assertFalse(isUdpPortInUse(srcPort)); + // assertFalse(isUdpPortInUse(srcPort2)); + + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent.expectDisconnected(); + mWiFiNetworkAgent = null; + } + + @Test + public void testTcpSocketKeepalives() throws Exception { + runTestWithSerialExecutors(executor -> doTestTcpSocketKeepalivesWithExecutor(executor)); + } + + private void doTestTcpSocketKeepalivesWithExecutor(Executor executor) throws Exception { + final int srcPortV4 = 12345; + final int srcPortV6 = 23456; + final InetAddress myIPv4 = InetAddress.getByName("127.0.0.1"); + final InetAddress myIPv6 = InetAddress.getByName("::1"); + + final int validKaInterval = 15; + + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("127.0.0.254"))); + + final Network notMyNet = new Network(61234); + final Network myNet = connectKeepaliveNetwork(lp); + + final Socket testSocketV4 = new Socket(); + final Socket testSocketV6 = new Socket(); + + TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor); + + // Attempt to start Tcp keepalives with invalid parameters and check for errors. + // Invalid network. + try (SocketKeepalive ka = mCm.createSocketKeepalive( + notMyNet, testSocketV4, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); + } + + // Invalid Socket (socket is not bound with IPv4 address). + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocketV4, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET); + } + + // Invalid Socket (socket is not bound with IPv6 address). + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocketV6, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET); + } + + // Bind the socket address + testSocketV4.bind(new InetSocketAddress(myIPv4, srcPortV4)); + testSocketV6.bind(new InetSocketAddress(myIPv6, srcPortV6)); + + // Invalid Socket (socket is bound with IPv4 address). + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocketV4, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET); + } + + // Invalid Socket (socket is bound with IPv6 address). + try (SocketKeepalive ka = mCm.createSocketKeepalive( + myNet, testSocketV6, executor, callback)) { + ka.start(validKaInterval); + callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET); + } + + testSocketV4.close(); + testSocketV6.close(); + + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent.expectDisconnected(); + mWiFiNetworkAgent = null; + } + + private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception { + final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + final InetAddress anyIPv4 = InetAddress.getByName("0.0.0.0"); + final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + final int validKaInterval = 15; + + // Prepare the target network. + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + Network myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); + mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS); + + TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor); + + // Prepare the target file descriptor, keep only one instance. + final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); + final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); + final int srcPort = testSocket.getPort(); + final ParcelFileDescriptor testPfd = + ParcelFileDescriptor.dup(testSocket.getFileDescriptor()); + testSocket.close(); + assertTrue(isUdpPortInUse(srcPort)); + + // Start keepalive and explicit make the variable goes out of scope with try-with-resources + // block. + try (SocketKeepalive ka = mCm.createNattKeepalive( + myNet, testPfd, myIPv4, dstIPv4, executor, callback)) { + ka.start(validKaInterval); + callback.expectStarted(); + ka.stop(); + callback.expectStopped(); + } + + // Check that the ParcelFileDescriptor is still valid after keepalive stopped, + // ErrnoException with EBADF will be thrown if the socket is closed when checking local + // address. + assertTrue(isUdpPortInUse(srcPort)); + final InetSocketAddress sa = + (InetSocketAddress) Os.getsockname(testPfd.getFileDescriptor()); + assertEquals(anyIPv4, sa.getAddress()); + + testPfd.close(); + // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7. + // assertFalse(isUdpPortInUse(srcPort)); + + mWiFiNetworkAgent.disconnect(); + mWiFiNetworkAgent.expectDisconnected(); + mWiFiNetworkAgent = null; + } + + private static boolean isUdpPortInUse(int port) { + try (DatagramSocket ignored = new DatagramSocket(port)) { + return false; + } catch (IOException alreadyInUse) { + return true; + } + } + + @Test + public void testGetCaptivePortalServerUrl() throws Exception { + String url = mCm.getCaptivePortalServerUrl(); + assertEquals("http://connectivitycheck.gstatic.com/generate_204", url); + } + + private static class TestNetworkPinner extends NetworkPinner { + public static boolean awaitPin(int timeoutMs) throws InterruptedException { + synchronized(sLock) { + if (sNetwork == null) { + sLock.wait(timeoutMs); + } + return sNetwork != null; + } + } + + public static boolean awaitUnpin(int timeoutMs) throws InterruptedException { + synchronized(sLock) { + if (sNetwork != null) { + sLock.wait(timeoutMs); + } + return sNetwork == null; + } + } + } + + private void assertPinnedToWifiWithCellDefault() { + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getBoundNetworkForProcess()); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + } + + private void assertPinnedToWifiWithWifiDefault() { + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getBoundNetworkForProcess()); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + } + + private void assertNotPinnedToWifi() { + assertNull(mCm.getBoundNetworkForProcess()); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + } + + @Test + public void testNetworkPinner() throws Exception { + NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI) + .build(); + assertNull(mCm.getBoundNetworkForProcess()); + + TestNetworkPinner.pin(mServiceContext, wifiRequest); + assertNull(mCm.getBoundNetworkForProcess()); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + + // When wi-fi connects, expect to be pinned. + assertTrue(TestNetworkPinner.awaitPin(100)); + assertPinnedToWifiWithCellDefault(); + + // Disconnect and expect the pin to drop. + mWiFiNetworkAgent.disconnect(); + assertTrue(TestNetworkPinner.awaitUnpin(100)); + assertNotPinnedToWifi(); + + // Reconnecting does not cause the pin to come back. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + assertFalse(TestNetworkPinner.awaitPin(100)); + assertNotPinnedToWifi(); + + // Pinning while connected causes the pin to take effect immediately. + TestNetworkPinner.pin(mServiceContext, wifiRequest); + assertTrue(TestNetworkPinner.awaitPin(100)); + assertPinnedToWifiWithCellDefault(); + + // Explicitly unpin and expect to use the default network again. + TestNetworkPinner.unpin(); + assertNotPinnedToWifi(); + + // Disconnect cell and wifi. + ExpectedBroadcast b = registerConnectivityBroadcast(3); // cell down, wifi up, wifi down. + mCellNetworkAgent.disconnect(); + mWiFiNetworkAgent.disconnect(); + b.expectBroadcast(); + + // Pinning takes effect even if the pinned network is the default when the pin is set... + TestNetworkPinner.pin(mServiceContext, wifiRequest); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + assertTrue(TestNetworkPinner.awaitPin(100)); + assertPinnedToWifiWithWifiDefault(); + + // ... and is maintained even when that network is no longer the default. + b = registerConnectivityBroadcast(1); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mCellNetworkAgent.connect(true); + b.expectBroadcast(); + assertPinnedToWifiWithCellDefault(); + } + + @Test + public void testNetworkCallbackMaximum() throws Exception { + final int MAX_REQUESTS = 100; + final int CALLBACKS = 89; + final int INTENTS = 11; + final int SYSTEM_ONLY_MAX_REQUESTS = 250; + assertEquals(MAX_REQUESTS, CALLBACKS + INTENTS); + + NetworkRequest networkRequest = new NetworkRequest.Builder().build(); + ArrayList registered = new ArrayList<>(); + + int j = 0; + while (j++ < CALLBACKS / 2) { + NetworkCallback cb = new NetworkCallback(); + mCm.requestNetwork(networkRequest, cb); + registered.add(cb); + } + while (j++ < CALLBACKS) { + NetworkCallback cb = new NetworkCallback(); + mCm.registerNetworkCallback(networkRequest, cb); + registered.add(cb); + } + j = 0; + while (j++ < INTENTS / 2) { + final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */, + new Intent("a" + j), FLAG_IMMUTABLE); + mCm.requestNetwork(networkRequest, pi); + registered.add(pi); + } + while (j++ < INTENTS) { + final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */, + new Intent("b" + j), FLAG_IMMUTABLE); + mCm.registerNetworkCallback(networkRequest, pi); + registered.add(pi); + } + + // Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added. + assertThrows(TooManyRequestsException.class, () -> + mCm.requestNetwork(networkRequest, new NetworkCallback()) + ); + assertThrows(TooManyRequestsException.class, () -> + mCm.registerNetworkCallback(networkRequest, new NetworkCallback()) + ); + assertThrows(TooManyRequestsException.class, () -> + mCm.requestNetwork(networkRequest, + PendingIntent.getBroadcast(mContext, 0 /* requestCode */, + new Intent("c"), FLAG_IMMUTABLE)) + ); + assertThrows(TooManyRequestsException.class, () -> + mCm.registerNetworkCallback(networkRequest, + PendingIntent.getBroadcast(mContext, 0 /* requestCode */, + new Intent("d"), FLAG_IMMUTABLE)) + ); + + // The system gets another SYSTEM_ONLY_MAX_REQUESTS slots. + final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> { + ArrayList systemRegistered = new ArrayList<>(); + for (int i = 0; i < SYSTEM_ONLY_MAX_REQUESTS - 1; i++) { + NetworkCallback cb = new NetworkCallback(); + if (i % 2 == 0) { + mCm.registerDefaultNetworkCallbackForUid(1000000 + i, cb, handler); + } else { + mCm.registerNetworkCallback(networkRequest, cb); + } + systemRegistered.add(cb); + } + waitForIdle(); + + assertThrows(TooManyRequestsException.class, () -> + mCm.registerDefaultNetworkCallbackForUid(1001042, new NetworkCallback(), + handler)); + assertThrows(TooManyRequestsException.class, () -> + mCm.registerNetworkCallback(networkRequest, new NetworkCallback())); + + for (NetworkCallback callback : systemRegistered) { + mCm.unregisterNetworkCallback(callback); + } + waitForIdle(); // Wait for requests to be unregistered before giving up the permission. + }); + + for (Object o : registered) { + if (o instanceof NetworkCallback) { + mCm.unregisterNetworkCallback((NetworkCallback)o); + } + if (o instanceof PendingIntent) { + mCm.unregisterNetworkCallback((PendingIntent)o); + } + } + waitForIdle(); + + // Test that the limit is not hit when MAX_REQUESTS requests are added and removed. + for (int i = 0; i < MAX_REQUESTS; i++) { + NetworkCallback networkCallback = new NetworkCallback(); + mCm.requestNetwork(networkRequest, networkCallback); + mCm.unregisterNetworkCallback(networkCallback); + } + waitForIdle(); + + for (int i = 0; i < MAX_REQUESTS; i++) { + NetworkCallback networkCallback = new NetworkCallback(); + mCm.registerNetworkCallback(networkRequest, networkCallback); + mCm.unregisterNetworkCallback(networkCallback); + } + waitForIdle(); + + for (int i = 0; i < MAX_REQUESTS; i++) { + NetworkCallback networkCallback = new NetworkCallback(); + mCm.registerDefaultNetworkCallback(networkCallback); + mCm.unregisterNetworkCallback(networkCallback); + } + waitForIdle(); + + for (int i = 0; i < MAX_REQUESTS; i++) { + NetworkCallback networkCallback = new NetworkCallback(); + mCm.registerDefaultNetworkCallback(networkCallback); + mCm.unregisterNetworkCallback(networkCallback); + } + waitForIdle(); + + withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> { + for (int i = 0; i < MAX_REQUESTS; i++) { + NetworkCallback networkCallback = new NetworkCallback(); + mCm.registerDefaultNetworkCallbackForUid(1000000 + i, networkCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + mCm.unregisterNetworkCallback(networkCallback); + } + }); + waitForIdle(); + + for (int i = 0; i < MAX_REQUESTS; i++) { + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, new Intent("e" + i), FLAG_IMMUTABLE); + mCm.requestNetwork(networkRequest, pendingIntent); + mCm.unregisterNetworkCallback(pendingIntent); + } + waitForIdle(); + + for (int i = 0; i < MAX_REQUESTS; i++) { + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, new Intent("f" + i), FLAG_IMMUTABLE); + mCm.registerNetworkCallback(networkRequest, pendingIntent); + mCm.unregisterNetworkCallback(pendingIntent); + } + } + + @Test + public void testNetworkInfoOfTypeNone() throws Exception { + ExpectedBroadcast b = registerConnectivityBroadcast(1); + + verifyNoNetwork(); + TestNetworkAgentWrapper wifiAware = new TestNetworkAgentWrapper(TRANSPORT_WIFI_AWARE); + assertNull(mCm.getActiveNetworkInfo()); + + Network[] allNetworks = mCm.getAllNetworks(); + assertLength(1, allNetworks); + Network network = allNetworks[0]; + NetworkCapabilities capabilities = mCm.getNetworkCapabilities(network); + assertTrue(capabilities.hasTransport(TRANSPORT_WIFI_AWARE)); + + final NetworkRequest request = + new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI_AWARE).build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + // Bring up wifi aware network. + wifiAware.connect(false, false, false /* isStrictMode */); + callback.expectAvailableCallbacksUnvalidated(wifiAware); + + assertNull(mCm.getActiveNetworkInfo()); + assertNull(mCm.getActiveNetwork()); + // TODO: getAllNetworkInfo is dirty and returns a non-empty array right from the start + // of this test. Fix it and uncomment the assert below. + //assertEmpty(mCm.getAllNetworkInfo()); + + // Disconnect wifi aware network. + wifiAware.disconnect(); + callback.expectCallbackThat(TIMEOUT_MS, (info) -> info instanceof CallbackEntry.Lost); + mCm.unregisterNetworkCallback(callback); + + verifyNoNetwork(); + b.expectNoBroadcast(10); + } + + @Test + public void testDeprecatedAndUnsupportedOperations() throws Exception { + final int TYPE_NONE = ConnectivityManager.TYPE_NONE; + assertNull(mCm.getNetworkInfo(TYPE_NONE)); + assertNull(mCm.getNetworkForType(TYPE_NONE)); + assertNull(mCm.getLinkProperties(TYPE_NONE)); + assertFalse(mCm.isNetworkSupported(TYPE_NONE)); + + assertThrows(IllegalArgumentException.class, + () -> mCm.networkCapabilitiesForType(TYPE_NONE)); + + Class unsupported = UnsupportedOperationException.class; + assertThrows(unsupported, () -> mCm.startUsingNetworkFeature(TYPE_WIFI, "")); + assertThrows(unsupported, () -> mCm.stopUsingNetworkFeature(TYPE_WIFI, "")); + // TODO: let test context have configuration application target sdk version + // and test that pre-M requesting for TYPE_NONE sends back APN_REQUEST_FAILED + assertThrows(unsupported, () -> mCm.startUsingNetworkFeature(TYPE_NONE, "")); + assertThrows(unsupported, () -> mCm.stopUsingNetworkFeature(TYPE_NONE, "")); + assertThrows(unsupported, () -> mCm.requestRouteToHostAddress(TYPE_NONE, null)); + } + + @Test + public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() throws Exception { + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(networkRequest, networkCallback); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(WIFI_IFNAME); + LinkAddress myIpv4Address = new LinkAddress("192.168.12.3/24"); + RouteInfo myIpv4DefaultRoute = new RouteInfo((IpPrefix) null, + InetAddresses.parseNumericAddress("192.168.12.1"), lp.getInterfaceName()); + lp.addLinkAddress(myIpv4Address); + lp.addRoute(myIpv4DefaultRoute); + + // Verify direct routes are added when network agent is first registered in + // ConnectivityService. + TestNetworkAgentWrapper networkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp); + networkAgent.connect(true); + networkCallback.expectCallback(CallbackEntry.AVAILABLE, networkAgent); + networkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, networkAgent); + CallbackEntry.LinkPropertiesChanged cbi = + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + networkAgent); + networkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, networkAgent); + networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent); + networkCallback.assertNoCallback(); + checkDirectlyConnectedRoutes(cbi.getLp(), Arrays.asList(myIpv4Address), + Arrays.asList(myIpv4DefaultRoute)); + checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()), + Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute)); + + // Verify direct routes are added during subsequent link properties updates. + LinkProperties newLp = new LinkProperties(lp); + LinkAddress myIpv6Address1 = new LinkAddress("fe80::cafe/64"); + LinkAddress myIpv6Address2 = new LinkAddress("2001:db8::2/64"); + newLp.addLinkAddress(myIpv6Address1); + newLp.addLinkAddress(myIpv6Address2); + networkAgent.sendLinkProperties(newLp); + cbi = networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, networkAgent); + networkCallback.assertNoCallback(); + checkDirectlyConnectedRoutes(cbi.getLp(), + Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2), + Arrays.asList(myIpv4DefaultRoute)); + mCm.unregisterNetworkCallback(networkCallback); + } + + private void assertSameElementsNoDuplicates(T[] expected, T[] actual) { + // Easier to implement than a proper "assertSameElements" method that also correctly deals + // with duplicates. + final String msg = Arrays.toString(expected) + " != " + Arrays.toString(actual); + assertEquals(msg, expected.length, actual.length); + Set expectedSet = new ArraySet<>(Arrays.asList(expected)); + assertEquals("expected contains duplicates", expectedSet.size(), expected.length); + // actual cannot have duplicates because it's the same length and has the same elements. + Set actualSet = new ArraySet<>(Arrays.asList(actual)); + assertEquals(expectedSet, actualSet); + } + + private void expectNetworkStatus(Network[] networks, String defaultIface, + Integer vpnUid, String vpnIfname, String[] underlyingIfaces) throws Exception { + ArgumentCaptor> networksCaptor = ArgumentCaptor.forClass(List.class); + ArgumentCaptor> vpnInfosCaptor = + ArgumentCaptor.forClass(List.class); + + verify(mStatsManager, atLeastOnce()).notifyNetworkStatus(networksCaptor.capture(), + any(List.class), eq(defaultIface), vpnInfosCaptor.capture()); + + assertSameElementsNoDuplicates(networksCaptor.getValue().toArray(), networks); + + UnderlyingNetworkInfo[] infos = + vpnInfosCaptor.getValue().toArray(new UnderlyingNetworkInfo[0]); + if (vpnUid != null) { + assertEquals("Should have exactly one VPN:", 1, infos.length); + UnderlyingNetworkInfo info = infos[0]; + assertEquals("Unexpected VPN owner:", (int) vpnUid, info.getOwnerUid()); + assertEquals("Unexpected VPN interface:", vpnIfname, info.getIface()); + assertSameElementsNoDuplicates(underlyingIfaces, + info.getUnderlyingIfaces().toArray(new String[0])); + } else { + assertEquals(0, infos.length); + return; + } + } + + private void expectNetworkStatus( + Network[] networks, String defaultIface) throws Exception { + expectNetworkStatus(networks, defaultIface, null, null, new String[0]); + } + + @Test + public void testStatsIfacesChanged() throws Exception { + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + + final Network[] onlyCell = new Network[] {mCellNetworkAgent.getNetwork()}; + final Network[] onlyWifi = new Network[] {mWiFiNetworkAgent.getNetwork()}; + + LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName(MOBILE_IFNAME); + LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName(WIFI_IFNAME); + + // Simple connection should have updated ifaces + mCellNetworkAgent.connect(false); + mCellNetworkAgent.sendLinkProperties(cellLp); + waitForIdle(); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); + + // Default network switch should update ifaces. + mWiFiNetworkAgent.connect(false); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + waitForIdle(); + assertEquals(wifiLp, mService.getActiveLinkProperties()); + expectNetworkStatus(onlyWifi, WIFI_IFNAME); + reset(mStatsManager); + + // Disconnect should update ifaces. + mWiFiNetworkAgent.disconnect(); + waitForIdle(); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); + + // Metered change should update ifaces + mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + waitForIdle(); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); + + mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + waitForIdle(); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); + + // Temp metered change shouldn't update ifaces + mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + waitForIdle(); + verify(mStatsManager, never()).notifyNetworkStatus(eq(Arrays.asList(onlyCell)), + any(List.class), eq(MOBILE_IFNAME), any(List.class)); + reset(mStatsManager); + + // Roaming change should update ifaces + mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); + waitForIdle(); + expectNetworkStatus(onlyCell, MOBILE_IFNAME); + reset(mStatsManager); + + // Test VPNs. + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(VPN_IFNAME); + + mMockVpn.establishForMyUid(lp); + assertUidRangesUpdatedForMyUid(true); + + final Network[] cellAndVpn = new Network[] { + mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; + + // A VPN with default (null) underlying networks sets the underlying network's interfaces... + expectNetworkStatus(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME}); + + // ...and updates them as the default network switches. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + final Network[] onlyNull = new Network[]{null}; + final Network[] wifiAndVpn = new Network[] { + mWiFiNetworkAgent.getNetwork(), mMockVpn.getNetwork()}; + final Network[] cellAndWifi = new Network[] { + mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork()}; + final Network[] cellNullAndWifi = new Network[] { + mCellNetworkAgent.getNetwork(), null, mWiFiNetworkAgent.getNetwork()}; + + waitForIdle(); + assertEquals(wifiLp, mService.getActiveLinkProperties()); + expectNetworkStatus(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{WIFI_IFNAME}); + reset(mStatsManager); + + // A VPN that sets its underlying networks passes the underlying interfaces, and influences + // the default interface sent to NetworkStatsService by virtue of applying to the system + // server UID (or, in this test, to the test's UID). This is the reason for sending + // MOBILE_IFNAME even though the default network is wifi. + // TODO: fix this to pass in the actual default network interface. Whether or not the VPN + // applies to the system server UID should not have any bearing on network stats. + mMockVpn.setUnderlyingNetworks(onlyCell); + waitForIdle(); + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME}); + reset(mStatsManager); + + mMockVpn.setUnderlyingNetworks(cellAndWifi); + waitForIdle(); + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + reset(mStatsManager); + + // Null underlying networks are ignored. + mMockVpn.setUnderlyingNetworks(cellNullAndWifi); + waitForIdle(); + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + reset(mStatsManager); + + // If an underlying network disconnects, that interface should no longer be underlying. + // This doesn't actually work because disconnectAndDestroyNetwork only notifies + // NetworkStatsService before the underlying network is actually removed. So the underlying + // network will only be removed if notifyIfacesChangedForNetworkStats is called again. This + // could result in incorrect data usage measurements if the interface used by the + // disconnected network is reused by a system component that does not register an agent for + // it (e.g., tethering). + mCellNetworkAgent.disconnect(); + waitForIdle(); + assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork())); + expectNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{MOBILE_IFNAME, WIFI_IFNAME}); + + // Confirm that we never tell NetworkStatsService that cell is no longer the underlying + // network for the VPN... + verify(mStatsManager, never()).notifyNetworkStatus(any(List.class), + any(List.class), any() /* anyString() doesn't match null */, + argThat(infos -> infos.get(0).getUnderlyingIfaces().size() == 1 + && WIFI_IFNAME.equals(infos.get(0).getUnderlyingIfaces().get(0)))); + verifyNoMoreInteractions(mStatsManager); + reset(mStatsManager); + + // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be + // called again, it does. For example, connect Ethernet, but with a low score, such that it + // does not become the default network. + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + mEthernetNetworkAgent.adjustScore(-40); + mEthernetNetworkAgent.connect(false); + waitForIdle(); + verify(mStatsManager).notifyNetworkStatus(any(List.class), + any(List.class), any() /* anyString() doesn't match null */, + argThat(vpnInfos -> vpnInfos.get(0).getUnderlyingIfaces().size() == 1 + && WIFI_IFNAME.equals(vpnInfos.get(0).getUnderlyingIfaces().get(0)))); + mEthernetNetworkAgent.disconnect(); + waitForIdle(); + reset(mStatsManager); + + // When a VPN declares no underlying networks (i.e., no connectivity), getAllVpnInfo + // does not return the VPN, so CS does not pass it to NetworkStatsService. This causes + // NetworkStatsFactory#adjustForTunAnd464Xlat not to attempt any VPN data migration, which + // is probably a performance improvement (though it's very unlikely that a VPN would declare + // no underlying networks). + // Also, for the same reason as above, the active interface passed in is null. + mMockVpn.setUnderlyingNetworks(new Network[0]); + waitForIdle(); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); + + // Specifying only a null underlying network is the same as no networks. + mMockVpn.setUnderlyingNetworks(onlyNull); + waitForIdle(); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); + + // Specifying networks that are all disconnected is the same as specifying no networks. + mMockVpn.setUnderlyingNetworks(onlyCell); + waitForIdle(); + expectNetworkStatus(wifiAndVpn, null); + reset(mStatsManager); + + // Passing in null again means follow the default network again. + mMockVpn.setUnderlyingNetworks(null); + waitForIdle(); + expectNetworkStatus(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, + new String[]{WIFI_IFNAME}); + reset(mStatsManager); + } + + @Test + public void testBasicDnsConfigurationPushed() throws Exception { + setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); + + // Clear any interactions that occur as a result of CS starting up. + reset(mMockDnsResolver); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + waitForIdle(); + verify(mMockDnsResolver, never()).setResolverConfiguration(any()); + verifyNoMoreInteractions(mMockDnsResolver); + + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName(MOBILE_IFNAME); + // Add IPv4 and IPv6 default routes, because DNS-over-TLS code does + // "is-reachable" testing in order to not program netd with unreachable + // nameservers that it might try repeated to validate. + cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24")); + cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"), + MOBILE_IFNAME)); + cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); + cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"), + MOBILE_IFNAME)); + mCellNetworkAgent.sendLinkProperties(cellLp); + mCellNetworkAgent.connect(false); + waitForIdle(); + + verify(mMockDnsResolver, times(1)).createNetworkCache( + eq(mCellNetworkAgent.getNetwork().netId)); + // CS tells dnsresolver about the empty DNS config for this network. + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any()); + reset(mMockDnsResolver); + + cellLp.addDnsServer(InetAddress.getByName("2001:db8::1")); + mCellNetworkAgent.sendLinkProperties(cellLp); + waitForIdle(); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue(); + assertEquals(1, resolvrParams.servers.length); + assertTrue(ArrayUtils.contains(resolvrParams.servers, "2001:db8::1")); + // Opportunistic mode. + assertTrue(ArrayUtils.contains(resolvrParams.tlsServers, "2001:db8::1")); + reset(mMockDnsResolver); + + cellLp.addDnsServer(InetAddress.getByName("192.0.2.1")); + mCellNetworkAgent.sendLinkProperties(cellLp); + waitForIdle(); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + resolvrParams = mResolverParamsParcelCaptor.getValue(); + assertEquals(2, resolvrParams.servers.length); + assertTrue(ArrayUtils.containsAll(resolvrParams.servers, + new String[]{"2001:db8::1", "192.0.2.1"})); + // Opportunistic mode. + assertEquals(2, resolvrParams.tlsServers.length); + assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, + new String[]{"2001:db8::1", "192.0.2.1"})); + reset(mMockDnsResolver); + + final String TLS_SPECIFIER = "tls.example.com"; + final String TLS_SERVER6 = "2001:db8:53::53"; + final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) }; + final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 }; + mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved( + new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel()); + + waitForIdle(); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + resolvrParams = mResolverParamsParcelCaptor.getValue(); + assertEquals(2, resolvrParams.servers.length); + assertTrue(ArrayUtils.containsAll(resolvrParams.servers, + new String[]{"2001:db8::1", "192.0.2.1"})); + reset(mMockDnsResolver); + } + + @Test + public void testDnsConfigurationTransTypesPushed() throws Exception { + // Clear any interactions that occur as a result of CS starting up. + reset(mMockDnsResolver); + + final NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + verify(mMockDnsResolver, times(1)).createNetworkCache( + eq(mWiFiNetworkAgent.getNetwork().netId)); + verify(mMockDnsResolver, times(2)).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + final ResolverParamsParcel resolverParams = mResolverParamsParcelCaptor.getValue(); + assertContainsExactly(resolverParams.transportTypes, TRANSPORT_WIFI); + reset(mMockDnsResolver); + } + + @Test + public void testPrivateDnsNotification() throws Exception { + NetworkRequest request = new NetworkRequest.Builder() + .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .build(); + TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + // Bring up wifi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + // Private DNS resolution failed, checking if the notification will be shown or not. + mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */); + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + waitForIdle(); + // If network validation failed, NetworkMonitor will re-evaluate the network. + // ConnectivityService should filter the redundant notification. This part is trying to + // simulate that situation and check if ConnectivityService could filter that case. + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + waitForIdle(); + verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).notify(anyString(), + eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any()); + // If private DNS resolution successful, the PRIVATE_DNS_BROKEN notification shouldn't be + // shown. + mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */); + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + waitForIdle(); + verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).cancel(anyString(), + eq(NotificationType.PRIVATE_DNS_BROKEN.eventId)); + // If private DNS resolution failed again, the PRIVATE_DNS_BROKEN notification should be + // shown again. + mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */); + mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + waitForIdle(); + verify(mNotificationManager, timeout(TIMEOUT_MS).times(2)).notify(anyString(), + eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any()); + } + + @Test + public void testPrivateDnsSettingsChange() throws Exception { + // Clear any interactions that occur as a result of CS starting up. + reset(mMockDnsResolver); + + // The default on Android is opportunistic mode ("Automatic"). + setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); + + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + waitForIdle(); + // CS tells netd about the empty DNS config for this network. + verify(mMockDnsResolver, never()).setResolverConfiguration(any()); + verifyNoMoreInteractions(mMockDnsResolver); + + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName(MOBILE_IFNAME); + // Add IPv4 and IPv6 default routes, because DNS-over-TLS code does + // "is-reachable" testing in order to not program netd with unreachable + // nameservers that it might try repeated to validate. + cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24")); + cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"), + MOBILE_IFNAME)); + cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); + cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"), + MOBILE_IFNAME)); + cellLp.addDnsServer(InetAddress.getByName("2001:db8::1")); + cellLp.addDnsServer(InetAddress.getByName("192.0.2.1")); + + mCellNetworkAgent.sendLinkProperties(cellLp); + mCellNetworkAgent.connect(false); + waitForIdle(); + verify(mMockDnsResolver, times(1)).createNetworkCache( + eq(mCellNetworkAgent.getNetwork().netId)); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue(); + assertEquals(2, resolvrParams.tlsServers.length); + assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, + new String[] { "2001:db8::1", "192.0.2.1" })); + // Opportunistic mode. + assertEquals(2, resolvrParams.tlsServers.length); + assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, + new String[] { "2001:db8::1", "192.0.2.1" })); + reset(mMockDnsResolver); + cellNetworkCallback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, + mCellNetworkAgent); + CallbackEntry.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback( + CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertFalse(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); + + setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); + verify(mMockDnsResolver, times(1)).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + resolvrParams = mResolverParamsParcelCaptor.getValue(); + assertEquals(2, resolvrParams.servers.length); + assertTrue(ArrayUtils.containsAll(resolvrParams.servers, + new String[] { "2001:db8::1", "192.0.2.1" })); + reset(mMockDnsResolver); + cellNetworkCallback.assertNoCallback(); + + setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + resolvrParams = mResolverParamsParcelCaptor.getValue(); + assertEquals(2, resolvrParams.servers.length); + assertTrue(ArrayUtils.containsAll(resolvrParams.servers, + new String[] { "2001:db8::1", "192.0.2.1" })); + assertEquals(2, resolvrParams.tlsServers.length); + assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, + new String[] { "2001:db8::1", "192.0.2.1" })); + reset(mMockDnsResolver); + cellNetworkCallback.assertNoCallback(); + + setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com"); + // Can't test dns configuration for strict mode without properly mocking + // out the DNS lookups, but can test that LinkProperties is updated. + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertTrue(cbi.getLp().isPrivateDnsActive()); + assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName()); + } + + private PrivateDnsValidationEventParcel makePrivateDnsValidationEvent( + final int netId, final String ipAddress, final String hostname, final int validation) { + final PrivateDnsValidationEventParcel event = new PrivateDnsValidationEventParcel(); + event.netId = netId; + event.ipAddress = ipAddress; + event.hostname = hostname; + event.validation = validation; + return event; + } + + @Test + public void testLinkPropertiesWithPrivateDnsValidationEvents() throws Exception { + // The default on Android is opportunistic mode ("Automatic"). + setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); + + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.requestNetwork(cellRequest, cellNetworkCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + waitForIdle(); + LinkProperties lp = new LinkProperties(); + mCellNetworkAgent.sendLinkProperties(lp); + mCellNetworkAgent.connect(false); + waitForIdle(); + cellNetworkCallback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, + mCellNetworkAgent); + CallbackEntry.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback( + CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertFalse(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); + Set dnsServers = new HashSet<>(); + checkDnsServers(cbi.getLp(), dnsServers); + + // Send a validation event for a server that is not part of the current + // resolver config. The validation event should be ignored. + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, "", + "145.100.185.18", VALIDATION_RESULT_SUCCESS)); + cellNetworkCallback.assertNoCallback(); + + // Add a dns server to the LinkProperties. + LinkProperties lp2 = new LinkProperties(lp); + lp2.addDnsServer(InetAddress.getByName("145.100.185.16")); + mCellNetworkAgent.sendLinkProperties(lp2); + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertFalse(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); + dnsServers.add(InetAddress.getByName("145.100.185.16")); + checkDnsServers(cbi.getLp(), dnsServers); + + // Send a validation event containing a hostname that is not part of + // the current resolver config. The validation event should be ignored. + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "hostname", VALIDATION_RESULT_SUCCESS)); + cellNetworkCallback.assertNoCallback(); + + // Send a validation event where validation failed. + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "", VALIDATION_RESULT_FAILURE)); + cellNetworkCallback.assertNoCallback(); + + // Send a validation event where validation succeeded for a server in + // the current resolver config. A LinkProperties callback with updated + // private dns fields should be sent. + mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( + makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, + "145.100.185.16", "", VALIDATION_RESULT_SUCCESS)); + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertTrue(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); + checkDnsServers(cbi.getLp(), dnsServers); + + // The private dns fields in LinkProperties should be preserved when + // the network agent sends unrelated changes. + LinkProperties lp3 = new LinkProperties(lp2); + lp3.setMtu(1300); + mCellNetworkAgent.sendLinkProperties(lp3); + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertTrue(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); + checkDnsServers(cbi.getLp(), dnsServers); + assertEquals(1300, cbi.getLp().getMtu()); + + // Removing the only validated server should affect the private dns + // fields in LinkProperties. + LinkProperties lp4 = new LinkProperties(lp3); + lp4.removeDnsServer(InetAddress.getByName("145.100.185.16")); + mCellNetworkAgent.sendLinkProperties(lp4); + cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + assertFalse(cbi.getLp().isPrivateDnsActive()); + assertNull(cbi.getLp().getPrivateDnsServerName()); + dnsServers.remove(InetAddress.getByName("145.100.185.16")); + checkDnsServers(cbi.getLp(), dnsServers); + assertEquals(1300, cbi.getLp().getMtu()); + } + + private void checkDirectlyConnectedRoutes(Object callbackObj, + Collection linkAddresses, Collection otherRoutes) { + assertTrue(callbackObj instanceof LinkProperties); + LinkProperties lp = (LinkProperties) callbackObj; + + Set expectedRoutes = new ArraySet<>(); + expectedRoutes.addAll(otherRoutes); + for (LinkAddress address : linkAddresses) { + RouteInfo localRoute = new RouteInfo(address, null, lp.getInterfaceName()); + // Duplicates in linkAddresses are considered failures + assertTrue(expectedRoutes.add(localRoute)); + } + List observedRoutes = lp.getRoutes(); + assertEquals(expectedRoutes.size(), observedRoutes.size()); + assertTrue(observedRoutes.containsAll(expectedRoutes)); + } + + private static void checkDnsServers(Object callbackObj, Set dnsServers) { + assertTrue(callbackObj instanceof LinkProperties); + LinkProperties lp = (LinkProperties) callbackObj; + assertEquals(dnsServers.size(), lp.getDnsServers().size()); + assertTrue(lp.getDnsServers().containsAll(dnsServers)); + } + + @Test + public void testApplyUnderlyingCapabilities() throws Exception { + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mCellNetworkAgent.connect(false /* validated */); + mWiFiNetworkAgent.connect(false /* validated */); + + final NetworkCapabilities cellNc = new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .setLinkDownstreamBandwidthKbps(10); + final NetworkCapabilities wifiNc = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_METERED) + .addCapability(NET_CAPABILITY_NOT_ROAMING) + .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .addCapability(NET_CAPABILITY_NOT_SUSPENDED) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .setLinkUpstreamBandwidthKbps(20); + mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */); + mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */); + waitForIdle(); + + final Network mobile = mCellNetworkAgent.getNetwork(); + final Network wifi = mWiFiNetworkAgent.getNetwork(); + + final NetworkCapabilities initialCaps = new NetworkCapabilities(); + initialCaps.addTransportType(TRANSPORT_VPN); + initialCaps.addCapability(NET_CAPABILITY_INTERNET); + initialCaps.removeCapability(NET_CAPABILITY_NOT_VPN); + + final NetworkCapabilities withNoUnderlying = new NetworkCapabilities(); + withNoUnderlying.addCapability(NET_CAPABILITY_INTERNET); + withNoUnderlying.addCapability(NET_CAPABILITY_NOT_CONGESTED); + withNoUnderlying.addCapability(NET_CAPABILITY_NOT_ROAMING); + withNoUnderlying.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + withNoUnderlying.addTransportType(TRANSPORT_VPN); + withNoUnderlying.removeCapability(NET_CAPABILITY_NOT_VPN); + + final NetworkCapabilities withMobileUnderlying = new NetworkCapabilities(withNoUnderlying); + withMobileUnderlying.addTransportType(TRANSPORT_CELLULAR); + withMobileUnderlying.removeCapability(NET_CAPABILITY_NOT_ROAMING); + withMobileUnderlying.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + withMobileUnderlying.setLinkDownstreamBandwidthKbps(10); + + final NetworkCapabilities withWifiUnderlying = new NetworkCapabilities(withNoUnderlying); + withWifiUnderlying.addTransportType(TRANSPORT_WIFI); + withWifiUnderlying.addCapability(NET_CAPABILITY_NOT_METERED); + withWifiUnderlying.setLinkUpstreamBandwidthKbps(20); + + final NetworkCapabilities withWifiAndMobileUnderlying = + new NetworkCapabilities(withNoUnderlying); + withWifiAndMobileUnderlying.addTransportType(TRANSPORT_CELLULAR); + withWifiAndMobileUnderlying.addTransportType(TRANSPORT_WIFI); + withWifiAndMobileUnderlying.removeCapability(NET_CAPABILITY_NOT_METERED); + withWifiAndMobileUnderlying.removeCapability(NET_CAPABILITY_NOT_ROAMING); + withWifiAndMobileUnderlying.setLinkDownstreamBandwidthKbps(10); + withWifiAndMobileUnderlying.setLinkUpstreamBandwidthKbps(20); + + final NetworkCapabilities initialCapsNotMetered = new NetworkCapabilities(initialCaps); + initialCapsNotMetered.addCapability(NET_CAPABILITY_NOT_METERED); + + NetworkCapabilities caps = new NetworkCapabilities(initialCaps); + mService.applyUnderlyingCapabilities(new Network[]{}, initialCapsNotMetered, caps); + assertEquals(withNoUnderlying, caps); + + caps = new NetworkCapabilities(initialCaps); + mService.applyUnderlyingCapabilities(new Network[]{null}, initialCapsNotMetered, caps); + assertEquals(withNoUnderlying, caps); + + caps = new NetworkCapabilities(initialCaps); + mService.applyUnderlyingCapabilities(new Network[]{mobile}, initialCapsNotMetered, caps); + assertEquals(withMobileUnderlying, caps); + + mService.applyUnderlyingCapabilities(new Network[]{wifi}, initialCapsNotMetered, caps); + assertEquals(withWifiUnderlying, caps); + + withWifiUnderlying.removeCapability(NET_CAPABILITY_NOT_METERED); + caps = new NetworkCapabilities(initialCaps); + mService.applyUnderlyingCapabilities(new Network[]{wifi}, initialCaps, caps); + assertEquals(withWifiUnderlying, caps); + + caps = new NetworkCapabilities(initialCaps); + mService.applyUnderlyingCapabilities(new Network[]{mobile, wifi}, initialCaps, caps); + assertEquals(withWifiAndMobileUnderlying, caps); + + withWifiUnderlying.addCapability(NET_CAPABILITY_NOT_METERED); + caps = new NetworkCapabilities(initialCaps); + mService.applyUnderlyingCapabilities(new Network[]{null, mobile, null, wifi}, + initialCapsNotMetered, caps); + assertEquals(withWifiAndMobileUnderlying, caps); + + caps = new NetworkCapabilities(initialCaps); + mService.applyUnderlyingCapabilities(new Network[]{null, mobile, null, wifi}, + initialCapsNotMetered, caps); + assertEquals(withWifiAndMobileUnderlying, caps); + + mService.applyUnderlyingCapabilities(null, initialCapsNotMetered, caps); + assertEquals(withWifiUnderlying, caps); + } + + @Test + public void testVpnConnectDisconnectUnderlyingNetwork() throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + final NetworkRequest request = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN).build(); + + mCm.registerNetworkCallback(request, callback); + + // Bring up a VPN that specifies an underlying network that does not exist yet. + // Note: it's sort of meaningless for a VPN app to declare a network that doesn't exist yet, + // (and doing so is difficult without using reflection) but it's good to test that the code + // behaves approximately correctly. + mMockVpn.establishForMyUid(false, true, false); + assertUidRangesUpdatedForMyUid(true); + final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId()); + mMockVpn.setUnderlyingNetworks(new Network[]{wifiNetwork}); + callback.expectAvailableCallbacksUnvalidated(mMockVpn); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_VPN)); + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_WIFI)); + + // Make that underlying network connect, and expect to see its capabilities immediately + // reflected in the VPN's capabilities. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + assertEquals(wifiNetwork, mWiFiNetworkAgent.getNetwork()); + mWiFiNetworkAgent.connect(false); + // TODO: the callback for the VPN happens before any callbacks are called for the wifi + // network that has just connected. There appear to be two issues here: + // 1. The VPN code will accept an underlying network as soon as getNetworkCapabilities() for + // it returns non-null (which happens very early, during handleRegisterNetworkAgent). + // This is not correct because that that point the network is not connected and cannot + // pass any traffic. + // 2. When a network connects, updateNetworkInfo propagates underlying network capabilities + // before rematching networks. + // Given that this scenario can't really happen, this is probably fine for now. + callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_VPN)); + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasTransport(TRANSPORT_WIFI)); + + // Disconnect the network, and expect to see the VPN capabilities change accordingly. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + callback.expectCapabilitiesThat(mMockVpn, (nc) -> + nc.getTransportTypes().length == 1 && nc.hasTransport(TRANSPORT_VPN)); + + mMockVpn.disconnect(); + mCm.unregisterNetworkCallback(callback); + } + + private void assertGetNetworkInfoOfGetActiveNetworkIsConnected(boolean expectedConnectivity) { + // What Chromium used to do before https://chromium-review.googlesource.com/2605304 + assertEquals("Unexpected result for getActiveNetworkInfo(getActiveNetwork())", + expectedConnectivity, mCm.getNetworkInfo(mCm.getActiveNetwork()).isConnected()); + } + + @Test + public void testVpnUnderlyingNetworkSuspended() throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + + // Connect a VPN. + mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */, + false /* isStrictMode */); + callback.expectAvailableCallbacksUnvalidated(mMockVpn); + + // Connect cellular data. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false /* validated */); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Suspend the cellular network and expect the VPN to be suspended. + mCellNetworkAgent.suspend(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + callback.assertNoCallback(); + + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + // VPN's main underlying network is suspended, so no connectivity. + assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + + // Switch to another network. The VPN should no longer be suspended. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false /* validated */); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_WIFI)); + callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Unsuspend cellular and then switch back to it. The VPN remains not suspended. + mCellNetworkAgent.resume(); + callback.assertNoCallback(); + mWiFiNetworkAgent.disconnect(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + // Spurious double callback? + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + + // Suspend cellular and expect no connectivity. + mCellNetworkAgent.suspend(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + callback.assertNoCallback(); + + assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + + // Resume cellular and expect that connectivity comes back. + mCellNetworkAgent.resume(); + callback.expectCapabilitiesThat(mMockVpn, + nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) + && nc.hasTransport(TRANSPORT_CELLULAR)); + callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + callback.assertNoCallback(); + + assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) + .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); + } + + @Test + public void testVpnNetworkActive() throws Exception { + // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback. + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final int uid = Process.myUid(); + + final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback genericNotVpnNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); + final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build(); + final NetworkRequest genericRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN).build(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_VPN).build(); + mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); + mCm.registerNetworkCallback(genericNotVpnRequest, genericNotVpnNetworkCallback); + mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); + mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); + mCm.registerDefaultNetworkCallback(defaultCallback); + mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + defaultCallback.assertNoCallback(); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + vpnNetworkCallback.assertNoCallback(); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + final Set ranges = uidRangesForUids(uid); + mMockVpn.registerAgent(ranges); + mMockVpn.setUnderlyingNetworks(new Network[0]); + + // VPN networks do not satisfy the default request and are automatically validated + // by NetworkMonitor + assertFalse(NetworkMonitorUtils.isValidationRequired( + mMockVpn.getAgent().getNetworkCapabilities())); + mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); + + mMockVpn.connect(false); + + genericNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + genericNotVpnNetworkCallback.assertNoCallback(); + wifiNetworkCallback.assertNoCallback(); + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(mWiFiNetworkAgent.getNetwork(), + systemDefaultCallback.getLastAvailableNetwork()); + + ranges.clear(); + mMockVpn.setUids(ranges); + + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + genericNotVpnNetworkCallback.assertNoCallback(); + wifiNetworkCallback.assertNoCallback(); + vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + + // TODO : The default network callback should actually get a LOST call here (also see the + // comment below for AVAILABLE). This is because ConnectivityService does not look at UID + // ranges at all when determining whether a network should be rematched. In practice, VPNs + // can't currently update their UIDs without disconnecting, so this does not matter too + // much, but that is the reason the test here has to check for an update to the + // capabilities instead of the expected LOST then AVAILABLE. + defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); + systemDefaultCallback.assertNoCallback(); + + ranges.add(new UidRange(uid, uid)); + mMockVpn.setUids(ranges); + + genericNetworkCallback.expectAvailableCallbacksValidated(mMockVpn); + genericNotVpnNetworkCallback.assertNoCallback(); + wifiNetworkCallback.assertNoCallback(); + vpnNetworkCallback.expectAvailableCallbacksValidated(mMockVpn); + // TODO : Here like above, AVAILABLE would be correct, but because this can't actually + // happen outside of the test, ConnectivityService does not rematch callbacks. + defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); + systemDefaultCallback.assertNoCallback(); + + mWiFiNetworkAgent.disconnect(); + + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + genericNotVpnNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + vpnNetworkCallback.assertNoCallback(); + defaultCallback.assertNoCallback(); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + + mMockVpn.disconnect(); + + genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + genericNotVpnNetworkCallback.assertNoCallback(); + wifiNetworkCallback.assertNoCallback(); + vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + systemDefaultCallback.assertNoCallback(); + assertEquals(null, mCm.getActiveNetwork()); + + mCm.unregisterNetworkCallback(genericNetworkCallback); + mCm.unregisterNetworkCallback(wifiNetworkCallback); + mCm.unregisterNetworkCallback(vpnNetworkCallback); + mCm.unregisterNetworkCallback(defaultCallback); + mCm.unregisterNetworkCallback(systemDefaultCallback); + } + + @Test + public void testVpnWithoutInternet() throws Exception { + final int uid = Process.myUid(); + + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + + defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, + false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); + + defaultCallback.assertNoCallback(); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mMockVpn.disconnect(); + defaultCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(defaultCallback); + } + + @Test + public void testVpnWithInternet() throws Exception { + final int uid = Process.myUid(); + + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + + defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */, + false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); + + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + + mMockVpn.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + + mCm.unregisterNetworkCallback(defaultCallback); + } + + @Test + public void testVpnUnvalidated() throws Exception { + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + + // Bring up Ethernet. + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + mEthernetNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); + callback.assertNoCallback(); + + // Bring up a VPN that has the INTERNET capability, initially unvalidated. + mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */, + false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); + + // Even though the VPN is unvalidated, it becomes the default network for our app. + callback.expectAvailableCallbacksUnvalidated(mMockVpn); + callback.assertNoCallback(); + + assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore()); + assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore()); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); + + NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED)); + assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET)); + + assertFalse(NetworkMonitorUtils.isValidationRequired( + mMockVpn.getAgent().getNetworkCapabilities())); + assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired( + mMockVpn.getAgent().getNetworkCapabilities())); + + // Pretend that the VPN network validates. + mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); + mMockVpn.getAgent().mNetworkMonitor.forceReevaluation(Process.myUid()); + // Expect to see the validated capability, but no other changes, because the VPN is already + // the default network for the app. + callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mMockVpn); + callback.assertNoCallback(); + + mMockVpn.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mMockVpn); + callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent); + } + + @Test + public void testVpnStartsWithUnderlyingCaps() throws Exception { + final int uid = Process.myUid(); + + final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); + final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_VPN) + .build(); + mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); + vpnNetworkCallback.assertNoCallback(); + + // Connect cell. It will become the default network, and in the absence of setting + // underlying networks explicitly it will become the sole underlying network for the vpn. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + mCellNetworkAgent.connect(true); + + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, + false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); + + vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(), + false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn.getNetwork(), TIMEOUT_MS, + nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)); + + final NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + assertTrue(nc.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED)); + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); + } + + private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) { + final NetworkCapabilities[] defaultCaps = mService.getDefaultNetworkCapabilitiesForUser( + userId, "com.android.calling.package", "com.test"); + final String defaultCapsString = Arrays.toString(defaultCaps); + assertEquals(defaultCapsString, defaultCaps.length, networks.length); + final Set defaultCapsSet = new ArraySet<>(defaultCaps); + for (NetworkAgentWrapper network : networks) { + final NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork()); + final String msg = "Did not find " + nc + " in " + Arrays.toString(defaultCaps); + assertTrue(msg, defaultCapsSet.contains(nc)); + } + } + + @Test + public void testVpnSetUnderlyingNetworks() throws Exception { + final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); + final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_VPN) + .build(); + NetworkCapabilities nc; + mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); + vpnNetworkCallback.assertNoCallback(); + + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, + false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); + + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + // For safety reasons a VPN without underlying networks is considered metered. + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + // A VPN without underlying networks is not suspended. + assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); + + final int userId = UserHandle.getUserId(Process.myUid()); + assertDefaultNetworkCapabilities(userId /* no networks */); + + // Connect cell and use it as an underlying network. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + mCellNetworkAgent.connect(true); + + mMockVpn.setUnderlyingNetworks( + new Network[] { mCellNetworkAgent.getNetwork() }); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + mWiFiNetworkAgent.connect(true); + + mMockVpn.setUnderlyingNetworks( + new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); + + // Don't disconnect, but note the VPN is not using wifi any more. + mMockVpn.setUnderlyingNetworks( + new Network[] { mCellNetworkAgent.getNetwork() }); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + // The return value of getDefaultNetworkCapabilitiesForUser always includes the default + // network (wifi) as well as the underlying networks (cell). + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); + + // Remove NOT_SUSPENDED from the only network and observe VPN is now suspended. + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + + // Add NOT_SUSPENDED again and observe VPN is no longer suspended. + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + + // Use Wifi but not cell. Note the VPN is now unmetered and not suspended. + mMockVpn.setUnderlyingNetworks( + new Network[] { mWiFiNetworkAgent.getNetwork() }); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) + && caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent); + + // Use both again. + mMockVpn.setUnderlyingNetworks( + new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); + + // Cell is suspended again. As WiFi is not, this should not cause a callback. + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); + vpnNetworkCallback.assertNoCallback(); + + // Stop using WiFi. The VPN is suspended again. + mMockVpn.setUnderlyingNetworks( + new Network[] { mCellNetworkAgent.getNetwork() }); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); + + // Use both again. + mMockVpn.setUnderlyingNetworks( + new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) + && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); + + // Disconnect cell. Receive update without even removing the dead network from the + // underlying networks – it's dead anyway. Not metered any more. + mCellNetworkAgent.disconnect(); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) + && caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent); + + // Disconnect wifi too. No underlying networks means this is now metered. + mWiFiNetworkAgent.disconnect(); + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + // When a network disconnects, the callbacks are fired before all state is updated, so for a + // short time, synchronous calls will behave as if the network is still connected. Wait for + // things to settle. + waitForIdle(); + assertDefaultNetworkCapabilities(userId /* no networks */); + + mMockVpn.disconnect(); + } + + @Test + public void testNullUnderlyingNetworks() throws Exception { + final int uid = Process.myUid(); + + final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); + final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .addTransportType(TRANSPORT_VPN) + .build(); + NetworkCapabilities nc; + mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); + vpnNetworkCallback.assertNoCallback(); + + mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, + false /* isStrictMode */); + assertUidRangesUpdatedForMyUid(true); + + vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + // By default, VPN is set to track default network (i.e. its underlying networks is null). + // In case of no default network, VPN is considered metered. + assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); + + // Connect to Cell; Cell is the default network. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + + // Connect to WiFi; WiFi is the new default. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) + && caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + + // Disconnect Cell. The default network did not change, so there shouldn't be any changes in + // the capabilities. + mCellNetworkAgent.disconnect(); + + // Disconnect wifi too. Now we have no default network. + mWiFiNetworkAgent.disconnect(); + + vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, + (caps) -> caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) + && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + + mMockVpn.disconnect(); + } + + @Test + public void testRestrictedProfileAffectsVpnUidRanges() throws Exception { + // NETWORK_SETTINGS is necessary to see the UID ranges in NetworkCapabilities. + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final NetworkRequest request = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + // Bring up a VPN + mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); + callback.expectAvailableThenValidatedCallbacks(mMockVpn); + callback.assertNoCallback(); + + final int uid = Process.myUid(); + NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertNotNull("nc=" + nc, nc.getUids()); + assertEquals(nc.getUids(), UidRange.toIntRanges(uidRangesForUids(uid))); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); + + // Set an underlying network and expect to see the VPN transports change. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCapabilitiesThat(mMockVpn, (caps) + -> caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_WIFI)); + callback.expectCapabilitiesThat(mWiFiNetworkAgent, (caps) + -> caps.hasCapability(NET_CAPABILITY_VALIDATED)); + + when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER)) + .thenReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)); + + final Intent addedIntent = new Intent(ACTION_USER_ADDED); + addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); + + // Send a USER_ADDED broadcast for it. + processBroadcast(addedIntent); + + // Expect that the VPN UID ranges contain both |uid| and the UID range for the newly-added + // restricted user. + final UidRange rRange = UidRange.createForUser(UserHandle.of(RESTRICTED_USER)); + final Range restrictUidRange = new Range(rRange.start, rRange.stop); + final Range singleUidRange = new Range(uid, uid); + callback.expectCapabilitiesThat(mMockVpn, (caps) + -> caps.getUids().size() == 2 + && caps.getUids().contains(singleUidRange) + && caps.getUids().contains(restrictUidRange) + && caps.hasTransport(TRANSPORT_VPN) + && caps.hasTransport(TRANSPORT_WIFI)); + + // Change the VPN's capabilities somehow (specifically, disconnect wifi). + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + callback.expectCapabilitiesThat(mMockVpn, (caps) + -> caps.getUids().size() == 2 + && caps.getUids().contains(singleUidRange) + && caps.getUids().contains(restrictUidRange) + && caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_WIFI)); + + // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user. + final Intent removedIntent = new Intent(ACTION_USER_REMOVED); + removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); + removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); + processBroadcast(removedIntent); + + // Expect that the VPN gains the UID range for the restricted user, and that the capability + // change made just before that (i.e., loss of TRANSPORT_WIFI) is preserved. + callback.expectCapabilitiesThat(mMockVpn, (caps) + -> caps.getUids().size() == 1 + && caps.getUids().contains(singleUidRange) + && caps.hasTransport(TRANSPORT_VPN) + && !caps.hasTransport(TRANSPORT_WIFI)); + } + + @Test + public void testLockdownVpnWithRestrictedProfiles() throws Exception { + // For ConnectivityService#setAlwaysOnVpnPackage. + mServiceContext.setPermission( + Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED); + // For call Vpn#setAlwaysOnPackage. + mServiceContext.setPermission( + Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); + // Necessary to see the UID ranges in NetworkCapabilities. + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final NetworkRequest request = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + final int uid = Process.myUid(); + + // Connect wifi and check that UIDs in the main and restricted profiles have network access. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true /* validated */); + final int restrictedUid = UserHandle.getUid(RESTRICTED_USER, 42 /* appId */); + assertNotNull(mCm.getActiveNetworkForUid(uid)); + assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); + + // Enable always-on VPN lockdown. The main user loses network access because no VPN is up. + final ArrayList allowList = new ArrayList<>(); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, + true /* lockdown */, allowList); + waitForIdle(); + assertNull(mCm.getActiveNetworkForUid(uid)); + // This is arguably overspecified: a UID that is not running doesn't have an active network. + // But it's useful to check that non-default users do not lose network access, and to prove + // that the loss of connectivity below is indeed due to the restricted profile coming up. + assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); + + // Start the restricted profile, and check that the UID within it loses network access. + when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER)) + .thenReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)); + when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO, + RESTRICTED_USER_INFO)); + // TODO: check that VPN app within restricted profile still has access, etc. + final Intent addedIntent = new Intent(ACTION_USER_ADDED); + addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); + processBroadcast(addedIntent); + assertNull(mCm.getActiveNetworkForUid(uid)); + assertNull(mCm.getActiveNetworkForUid(restrictedUid)); + + // Stop the restricted profile, and check that the UID within it has network access again. + when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO)); + + // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user. + final Intent removedIntent = new Intent(ACTION_USER_REMOVED); + removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); + removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); + processBroadcast(removedIntent); + assertNull(mCm.getActiveNetworkForUid(uid)); + assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); + + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, + allowList); + waitForIdle(); + } + + @Test + public void testIsActiveNetworkMeteredOverWifi() throws Exception { + // Returns true by default when no network is available. + assertTrue(mCm.isActiveNetworkMetered()); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + + assertFalse(mCm.isActiveNetworkMetered()); + } + + @Test + public void testIsActiveNetworkMeteredOverCell() throws Exception { + // Returns true by default when no network is available. + assertTrue(mCm.isActiveNetworkMetered()); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + mCellNetworkAgent.connect(true); + waitForIdle(); + + assertTrue(mCm.isActiveNetworkMetered()); + } + + @Test + public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() throws Exception { + // Returns true by default when no network is available. + assertTrue(mCm.isActiveNetworkMetered()); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + mCellNetworkAgent.connect(true); + waitForIdle(); + assertTrue(mCm.isActiveNetworkMetered()); + + // Connect VPN network. By default it is using current default network (Cell). + mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); + + // Ensure VPN is now the active network. + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); + + // Expect VPN to be metered. + assertTrue(mCm.isActiveNetworkMetered()); + + // Connect WiFi. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + // VPN should still be the active network. + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); + + // Expect VPN to be unmetered as it should now be using WiFi (new default). + assertFalse(mCm.isActiveNetworkMetered()); + + // Disconnecting Cell should not affect VPN's meteredness. + mCellNetworkAgent.disconnect(); + waitForIdle(); + + assertFalse(mCm.isActiveNetworkMetered()); + + // Disconnect WiFi; Now there is no platform default network. + mWiFiNetworkAgent.disconnect(); + waitForIdle(); + + // VPN without any underlying networks is treated as metered. + assertTrue(mCm.isActiveNetworkMetered()); + + mMockVpn.disconnect(); + } + + @Test + public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() throws Exception { + // Returns true by default when no network is available. + assertTrue(mCm.isActiveNetworkMetered()); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + mCellNetworkAgent.connect(true); + waitForIdle(); + assertTrue(mCm.isActiveNetworkMetered()); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertFalse(mCm.isActiveNetworkMetered()); + + // Connect VPN network. + mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); + + // Ensure VPN is now the active network. + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); + // VPN is using Cell + mMockVpn.setUnderlyingNetworks( + new Network[] { mCellNetworkAgent.getNetwork() }); + waitForIdle(); + + // Expect VPN to be metered. + assertTrue(mCm.isActiveNetworkMetered()); + + // VPN is now using WiFi + mMockVpn.setUnderlyingNetworks( + new Network[] { mWiFiNetworkAgent.getNetwork() }); + waitForIdle(); + + // Expect VPN to be unmetered + assertFalse(mCm.isActiveNetworkMetered()); + + // VPN is using Cell | WiFi. + mMockVpn.setUnderlyingNetworks( + new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); + waitForIdle(); + + // Expect VPN to be metered. + assertTrue(mCm.isActiveNetworkMetered()); + + // VPN is using WiFi | Cell. + mMockVpn.setUnderlyingNetworks( + new Network[] { mWiFiNetworkAgent.getNetwork(), mCellNetworkAgent.getNetwork() }); + waitForIdle(); + + // Order should not matter and VPN should still be metered. + assertTrue(mCm.isActiveNetworkMetered()); + + // VPN is not using any underlying networks. + mMockVpn.setUnderlyingNetworks(new Network[0]); + waitForIdle(); + + // VPN without underlying networks is treated as metered. + assertTrue(mCm.isActiveNetworkMetered()); + + mMockVpn.disconnect(); + } + + @Test + public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() throws Exception { + // Returns true by default when no network is available. + assertTrue(mCm.isActiveNetworkMetered()); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertFalse(mCm.isActiveNetworkMetered()); + + // Connect VPN network. + mMockVpn.registerAgent(true /* isAlwaysMetered */, uidRangesForUids(Process.myUid()), + new LinkProperties()); + mMockVpn.connect(true); + waitForIdle(); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); + + // VPN is tracking current platform default (WiFi). + mMockVpn.setUnderlyingNetworks(null); + waitForIdle(); + + // Despite VPN using WiFi (which is unmetered), VPN itself is marked as always metered. + assertTrue(mCm.isActiveNetworkMetered()); + + + // VPN explicitly declares WiFi as its underlying network. + mMockVpn.setUnderlyingNetworks( + new Network[] { mWiFiNetworkAgent.getNetwork() }); + waitForIdle(); + + // Doesn't really matter whether VPN declares its underlying networks explicitly. + assertTrue(mCm.isActiveNetworkMetered()); + + // With WiFi lost, VPN is basically without any underlying networks. And in that case it is + // anyways suppose to be metered. + mWiFiNetworkAgent.disconnect(); + waitForIdle(); + + assertTrue(mCm.isActiveNetworkMetered()); + + mMockVpn.disconnect(); + } + + private class DetailedBlockedStatusCallback extends TestNetworkCallback { + public void expectAvailableThenValidatedCallbacks(HasNetwork n, int blockedStatus) { + super.expectAvailableThenValidatedCallbacks(n.getNetwork(), blockedStatus, TIMEOUT_MS); + } + public void expectBlockedStatusCallback(HasNetwork n, int blockedStatus) { + // This doesn't work: + // super.expectBlockedStatusCallback(blockedStatus, n.getNetwork()); + super.expectBlockedStatusCallback(blockedStatus, n.getNetwork(), TIMEOUT_MS); + } + public void onBlockedStatusChanged(Network network, int blockedReasons) { + getHistory().add(new CallbackEntry.BlockedStatusInt(network, blockedReasons)); + } + } + + @Test + public void testNetworkBlockedStatus() throws Exception { + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .build(); + mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); + final DetailedBlockedStatusCallback detailedCallback = new DetailedBlockedStatusCallback(); + mCm.registerNetworkCallback(cellRequest, detailedCallback); + + mockUidNetworkingBlocked(); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + detailedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent, + BLOCKED_REASON_NONE); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); + + setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, + BLOCKED_REASON_BATTERY_SAVER); + assertNull(mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); + + // If blocked state does not change but blocked reason does, the boolean callback is called. + // TODO: investigate de-duplicating. + setBlockedReasonChanged(BLOCKED_METERED_REASON_USER_RESTRICTED); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, + BLOCKED_METERED_REASON_USER_RESTRICTED); + + setBlockedReasonChanged(BLOCKED_REASON_NONE); + cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); + + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, + BLOCKED_METERED_REASON_DATA_SAVER); + assertNull(mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); + + // Restrict the network based on UID rule and NOT_METERED capability change. + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); + cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + detailedCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); + + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, + mCellNetworkAgent); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + detailedCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, + mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, + BLOCKED_METERED_REASON_DATA_SAVER); + assertNull(mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); + + setBlockedReasonChanged(BLOCKED_REASON_NONE); + cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); + + setBlockedReasonChanged(BLOCKED_REASON_NONE); + cellNetworkCallback.assertNoCallback(); + detailedCallback.assertNoCallback(); + + // Restrict background data. Networking is not blocked because the network is unmetered. + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, + BLOCKED_METERED_REASON_DATA_SAVER); + assertNull(mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); + cellNetworkCallback.assertNoCallback(); + + setBlockedReasonChanged(BLOCKED_REASON_NONE); + cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); + + setBlockedReasonChanged(BLOCKED_REASON_NONE); + cellNetworkCallback.assertNoCallback(); + detailedCallback.assertNoCallback(); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); + + mCm.unregisterNetworkCallback(cellNetworkCallback); + } + + @Test + public void testNetworkBlockedStatusBeforeAndAfterConnect() throws Exception { + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + mockUidNetworkingBlocked(); + + // No Networkcallbacks invoked before any network is active. + setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER); + setBlockedReasonChanged(BLOCKED_REASON_NONE); + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); + defaultCallback.assertNoCallback(); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent); + + // Allow to use the network after switching to NOT_METERED network. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + + // Switch to METERED network. Restrict the use of the network. + mWiFiNetworkAgent.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellNetworkAgent); + + // Network becomes NOT_METERED. + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); + defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + + // Verify there's no Networkcallbacks invoked after data saver on/off. + setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); + setBlockedReasonChanged(BLOCKED_REASON_NONE); + defaultCallback.assertNoCallback(); + + mCellNetworkAgent.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(defaultCallback); + } + + private void expectNetworkRejectNonSecureVpn(InOrder inOrder, boolean add, + UidRangeParcel... expected) throws Exception { + inOrder.verify(mMockNetd).networkRejectNonSecureVpn(eq(add), aryEq(expected)); + } + + private void checkNetworkInfo(NetworkInfo ni, int type, DetailedState state) { + assertNotNull(ni); + assertEquals(type, ni.getType()); + assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState()); + if (state == DetailedState.CONNECTED || state == DetailedState.SUSPENDED) { + assertNotNull(ni.getExtraInfo()); + } else { + // Technically speaking, a network that's in CONNECTING state will generally have a + // non-null extraInfo. This doesn't actually happen in this test because it never calls + // a legacy API while a network is connecting. When a network is in CONNECTING state + // because of legacy lockdown VPN, its extraInfo is always null. + assertNull(ni.getExtraInfo()); + } + } + + private void assertActiveNetworkInfo(int type, DetailedState state) { + checkNetworkInfo(mCm.getActiveNetworkInfo(), type, state); + } + private void assertNetworkInfo(int type, DetailedState state) { + checkNetworkInfo(mCm.getNetworkInfo(type), type, state); + } + + private void assertExtraInfoFromCm(TestNetworkAgentWrapper network, boolean present) { + final NetworkInfo niForNetwork = mCm.getNetworkInfo(network.getNetwork()); + final NetworkInfo niForType = mCm.getNetworkInfo(network.getLegacyType()); + if (present) { + assertEquals(network.getExtraInfo(), niForNetwork.getExtraInfo()); + assertEquals(network.getExtraInfo(), niForType.getExtraInfo()); + } else { + assertNull(niForNetwork.getExtraInfo()); + assertNull(niForType.getExtraInfo()); + } + } + + private void assertExtraInfoFromCmBlocked(TestNetworkAgentWrapper network) { + assertExtraInfoFromCm(network, false); + } + + private void assertExtraInfoFromCmPresent(TestNetworkAgentWrapper network) { + assertExtraInfoFromCm(network, true); + } + + // Checks that each of the |agents| receive a blocked status change callback with the specified + // |blocked| value, in any order. This is needed because when an event affects multiple + // networks, ConnectivityService does not guarantee the order in which callbacks are fired. + private void assertBlockedCallbackInAnyOrder(TestNetworkCallback callback, boolean blocked, + TestNetworkAgentWrapper... agents) { + final List expectedNetworks = Arrays.asList(agents).stream() + .map((agent) -> agent.getNetwork()) + .collect(Collectors.toList()); + + // Expect exactly one blocked callback for each agent. + for (int i = 0; i < agents.length; i++) { + CallbackEntry e = callback.expectCallbackThat(TIMEOUT_MS, (c) -> + c instanceof CallbackEntry.BlockedStatus + && ((CallbackEntry.BlockedStatus) c).getBlocked() == blocked); + Network network = e.getNetwork(); + assertTrue("Received unexpected blocked callback for network " + network, + expectedNetworks.remove(network)); + } + } + + @Test + public void testNetworkBlockedStatusAlwaysOnVpn() throws Exception { + mServiceContext.setPermission( + Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED); + mServiceContext.setPermission( + Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final TestNetworkCallback callback = new TestNetworkCallback(); + final NetworkRequest request = new NetworkRequest.Builder() + .removeCapability(NET_CAPABILITY_NOT_VPN) + .build(); + mCm.registerNetworkCallback(request, callback); + + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + final TestNetworkCallback vpnUidCallback = new TestNetworkCallback(); + final NetworkRequest vpnUidRequest = new NetworkRequest.Builder().build(); + registerNetworkCallbackAsUid(vpnUidRequest, vpnUidCallback, VPN_UID); + + final TestNetworkCallback vpnUidDefaultCallback = new TestNetworkCallback(); + registerDefaultNetworkCallbackAsUid(vpnUidDefaultCallback, VPN_UID); + + final TestNetworkCallback vpnDefaultCallbackAsUid = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallbackForUid(VPN_UID, vpnDefaultCallbackAsUid, + new Handler(ConnectivityThread.getInstanceLooper())); + + final int uid = Process.myUid(); + final int userId = UserHandle.getUserId(uid); + final ArrayList allowList = new ArrayList<>(); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); + waitForIdle(); + + UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1); + UidRangeParcel secondHalf = new UidRangeParcel(VPN_UID + 1, 99999); + InOrder inOrder = inOrder(mMockNetd); + expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); + + // Connect a network when lockdown is active, expect to see it blocked. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false /* validated */); + callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); + vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + vpnDefaultCallbackAsUid.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertNull(mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + // Mobile is BLOCKED even though it's not actually connected. + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + + // Disable lockdown, expect to see the network unblocked. + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); + defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); + vpnUidCallback.assertNoCallback(); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + + // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked. + allowList.add(TEST_PACKAGE_NAME); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); + callback.assertNoCallback(); + defaultCallback.assertNoCallback(); + vpnUidCallback.assertNoCallback(); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + + // The following requires that the UID of this test package is greater than VPN_UID. This + // is always true in practice because a plain AOSP build with no apps installed has almost + // 200 packages installed. + final UidRangeParcel piece1 = new UidRangeParcel(1, VPN_UID - 1); + final UidRangeParcel piece2 = new UidRangeParcel(VPN_UID + 1, uid - 1); + final UidRangeParcel piece3 = new UidRangeParcel(uid + 1, 99999); + expectNetworkRejectNonSecureVpn(inOrder, true, piece1, piece2, piece3); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + + // Connect a new network, expect it to be unblocked. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false /* validated */); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + defaultCallback.assertNoCallback(); + vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + // Cellular is DISCONNECTED because it's not the default and there are no requests for it. + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + + // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown. + // Everything should now be blocked. + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + waitForIdle(); + expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3); + allowList.clear(); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); + waitForIdle(); + expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); + defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); + assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); + vpnUidCallback.assertNoCallback(); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertNull(mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + + // Disable lockdown. Everything is unblocked. + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); + assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); + vpnUidCallback.assertNoCallback(); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + + // Enable and disable an always-on VPN package without lockdown. Expect no changes. + reset(mMockNetd); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, + allowList); + inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); + callback.assertNoCallback(); + defaultCallback.assertNoCallback(); + vpnUidCallback.assertNoCallback(); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); + callback.assertNoCallback(); + defaultCallback.assertNoCallback(); + vpnUidCallback.assertNoCallback(); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + + // Enable lockdown and connect a VPN. The VPN is not blocked. + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); + defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); + assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); + vpnUidCallback.assertNoCallback(); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertNull(mCm.getActiveNetwork()); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + + mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability. + vpnUidDefaultCallback.assertNoCallback(); // VPN does not apply to VPN_UID + vpnDefaultCallbackAsUid.assertNoCallback(); + assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + + mMockVpn.disconnect(); + defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); + vpnUidCallback.assertNoCallback(); + vpnUidDefaultCallback.assertNoCallback(); + vpnDefaultCallbackAsUid.assertNoCallback(); + assertNull(mCm.getActiveNetwork()); + + mCm.unregisterNetworkCallback(callback); + mCm.unregisterNetworkCallback(defaultCallback); + mCm.unregisterNetworkCallback(vpnUidCallback); + mCm.unregisterNetworkCallback(vpnUidDefaultCallback); + mCm.unregisterNetworkCallback(vpnDefaultCallbackAsUid); + } + + private void setupLegacyLockdownVpn() { + final String profileName = "testVpnProfile"; + final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8); + when(mVpnProfileStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag); + + final VpnProfile profile = new VpnProfile(profileName); + profile.name = "My VPN"; + profile.server = "192.0.2.1"; + profile.dnsServers = "8.8.8.8"; + profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK; + final byte[] encodedProfile = profile.encode(); + when(mVpnProfileStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); + } + + private void establishLegacyLockdownVpn(Network underlying) throws Exception { + // The legacy lockdown VPN only supports userId 0, and must have an underlying network. + assertNotNull(underlying); + mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY); + // The legacy lockdown VPN only supports userId 0. + final Set ranges = Collections.singleton(PRIMARY_UIDRANGE); + mMockVpn.registerAgent(ranges); + mMockVpn.setUnderlyingNetworks(new Network[]{underlying}); + mMockVpn.connect(true); + } + + @Test + public void testLegacyLockdownVpn() throws Exception { + mServiceContext.setPermission( + Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); + // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback. + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + + // Pretend lockdown VPN was configured. + setupLegacyLockdownVpn(); + + // LockdownVpnTracker disables the Vpn teardown code and enables lockdown. + // Check the VPN's state before it does so. + assertTrue(mMockVpn.getEnableTeardown()); + assertFalse(mMockVpn.getLockdown()); + + // Send a USER_UNLOCKED broadcast so CS starts LockdownVpnTracker. + final int userId = UserHandle.getUserId(Process.myUid()); + final Intent addedIntent = new Intent(ACTION_USER_UNLOCKED); + addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + processBroadcast(addedIntent); + + // Lockdown VPN disables teardown and enables lockdown. + assertFalse(mMockVpn.getEnableTeardown()); + assertTrue(mMockVpn.getLockdown()); + + // Bring up a network. + // Expect nothing to happen because the network does not have an IPv4 default route: legacy + // VPN only supports IPv4. + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName("rmnet0"); + cellLp.addLinkAddress(new LinkAddress("2001:db8::1/64")); + cellLp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, "rmnet0")); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + mCellNetworkAgent.connect(false /* validated */); + callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + waitForIdle(); + assertNull(mMockVpn.getAgent()); + + // Add an IPv4 address. Ideally the VPN should start, but it doesn't because nothing calls + // LockdownVpnTracker#handleStateChangedLocked. This is a bug. + // TODO: consider fixing this. + cellLp.addLinkAddress(new LinkAddress("192.0.2.2/25")); + cellLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "rmnet0")); + mCellNetworkAgent.sendLinkProperties(cellLp); + callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); + waitForIdle(); + assertNull(mMockVpn.getAgent()); + + // Disconnect, then try again with a network that supports IPv4 at connection time. + // Expect lockdown VPN to come up. + ExpectedBroadcast b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); + mCellNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + b1.expectBroadcast(); + + // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten + // with the state of the VPN network. So expect a CONNECTING broadcast. + b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTING); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + mCellNetworkAgent.connect(false /* validated */); + callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + b1.expectBroadcast(); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mCellNetworkAgent); + + // TODO: it would be nice if we could simply rely on the production code here, and have + // LockdownVpnTracker start the VPN, have the VPN code register its NetworkAgent with + // ConnectivityService, etc. That would require duplicating a fair bit of code from the + // Vpn tests around how to mock out LegacyVpnRunner. But even if we did that, this does not + // work for at least two reasons: + // 1. In this test, calling registerNetworkAgent does not actually result in an agent being + // registered. This is because nothing calls onNetworkMonitorCreated, which is what + // actually ends up causing handleRegisterNetworkAgent to be called. Code in this test + // that wants to register an agent must use TestNetworkAgentWrapper. + // 2. Even if we exposed Vpn#agentConnect to the test, and made MockVpn#agentConnect call + // the TestNetworkAgentWrapper code, this would deadlock because the + // TestNetworkAgentWrapper code cannot be called on the handler thread since it calls + // waitForIdle(). + mMockVpn.expectStartLegacyVpnRunner(); + b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); + ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); + establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork()); + callback.expectAvailableThenValidatedCallbacks(mMockVpn); + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); + NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + b1.expectBroadcast(); + b2.expectBroadcast(); + assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mCellNetworkAgent); + assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); + assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); + assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY); + + // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect. + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName("wlan0"); + wifiLp.addLinkAddress(new LinkAddress("192.0.2.163/25")); + wifiLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "wlan0")); + final NetworkCapabilities wifiNc = new NetworkCapabilities(); + wifiNc.addTransportType(TRANSPORT_WIFI); + wifiNc.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc); + + b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); + // Wifi is CONNECTING because the VPN isn't up yet. + b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTING); + ExpectedBroadcast b3 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); + mWiFiNetworkAgent.connect(false /* validated */); + b1.expectBroadcast(); + b2.expectBroadcast(); + b3.expectBroadcast(); + mMockVpn.expectStopVpnRunnerPrivileged(); + mMockVpn.expectStartLegacyVpnRunner(); + + // TODO: why is wifi not blocked? Is it because when this callback is sent, the VPN is still + // connected, so the network is not considered blocked by the lockdown UID ranges? But the + // fact that a VPN is connected should only result in the VPN itself being unblocked, not + // any other network. Bug in isUidBlockedByVpn? + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + callback.expectCallback(CallbackEntry.LOST, mMockVpn); + defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + // While the VPN is reconnecting on the new network, everything is blocked. + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); + assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED); + assertExtraInfoFromCmBlocked(mWiFiNetworkAgent); + + // The VPN comes up again on wifi. + b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); + b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); + establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork()); + callback.expectAvailableThenValidatedCallbacks(mMockVpn); + defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); + b1.expectBroadcast(); + b2.expectBroadcast(); + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mWiFiNetworkAgent); + vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); + assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI)); + assertFalse(vpnNc.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); + + // Disconnect cell. Nothing much happens since it's not the default network. + mCellNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + defaultCallback.assertNoCallback(); + systemDefaultCallback.assertNoCallback(); + + assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); + assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertExtraInfoFromCmPresent(mWiFiNetworkAgent); + + b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); + b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + b1.expectBroadcast(); + callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI)); + mMockVpn.expectStopVpnRunnerPrivileged(); + callback.expectCallback(CallbackEntry.LOST, mMockVpn); + b2.expectBroadcast(); + } + + /** + * Test mutable and requestable network capabilities such as + * {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_VCN_MANAGED}. Verify that the + * {@code ConnectivityService} re-assign the networks accordingly. + */ + @Test + public final void testLoseMutableAndRequestableCaps() throws Exception { + final int[] testCaps = new int [] { + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_NOT_VCN_MANAGED + }; + for (final int testCap : testCaps) { + // Create requests with and without the testing capability. + final TestNetworkCallback callbackWithCap = new TestNetworkCallback(); + final TestNetworkCallback callbackWithoutCap = new TestNetworkCallback(); + mCm.requestNetwork(new NetworkRequest.Builder().addCapability(testCap).build(), + callbackWithCap); + mCm.requestNetwork(new NetworkRequest.Builder().removeCapability(testCap).build(), + callbackWithoutCap); + + // Setup networks with testing capability and verify the default network changes. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(testCap); + mCellNetworkAgent.connect(true); + callbackWithCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callbackWithoutCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(testCap); + mWiFiNetworkAgent.connect(true); + callbackWithCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callbackWithoutCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + + // Remove the testing capability on wifi, verify the callback and default network + // changes back to cellular. + mWiFiNetworkAgent.removeCapability(testCap); + callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); + callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + + mCellNetworkAgent.removeCapability(testCap); + callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + callbackWithoutCap.assertNoCallback(); + verify(mMockNetd).networkClearDefault(); + + mCm.unregisterNetworkCallback(callbackWithCap); + mCm.unregisterNetworkCallback(callbackWithoutCap); + } + } + + @Test + public final void testBatteryStatsNetworkType() throws Exception { + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName("cell0"); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + mCellNetworkAgent.connect(true); + waitForIdle(); + verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext, + cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); + + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName("wifi0"); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext, + wifiLp.getInterfaceName(), + new int[] { TRANSPORT_WIFI }); + + mCellNetworkAgent.disconnect(); + mWiFiNetworkAgent.disconnect(); + + cellLp.setInterfaceName("wifi0"); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + mCellNetworkAgent.connect(true); + waitForIdle(); + verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext, + cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); + mCellNetworkAgent.disconnect(); + } + + /** + * Make simulated InterfaceConfigParcel for Nat464Xlat to query clat lower layer info. + */ + private InterfaceConfigurationParcel getClatInterfaceConfigParcel(LinkAddress la) { + final InterfaceConfigurationParcel cfg = new InterfaceConfigurationParcel(); + cfg.hwAddr = "11:22:33:44:55:66"; + cfg.ipv4Addr = la.getAddress().getHostAddress(); + cfg.prefixLength = la.getPrefixLength(); + return cfg; + } + + /** + * Make expected stack link properties, copied from Nat464Xlat. + */ + private LinkProperties makeClatLinkProperties(LinkAddress la) { + LinkAddress clatAddress = la; + LinkProperties stacked = new LinkProperties(); + stacked.setInterfaceName(CLAT_PREFIX + MOBILE_IFNAME); + RouteInfo ipv4Default = new RouteInfo( + new LinkAddress(Inet4Address.ANY, 0), + clatAddress.getAddress(), CLAT_PREFIX + MOBILE_IFNAME); + stacked.addRoute(ipv4Default); + stacked.addLinkAddress(clatAddress); + return stacked; + } + + private Nat64PrefixEventParcel makeNat64PrefixEvent(final int netId, final int prefixOperation, + final String prefixAddress, final int prefixLength) { + final Nat64PrefixEventParcel event = new Nat64PrefixEventParcel(); + event.netId = netId; + event.prefixOperation = prefixOperation; + event.prefixAddress = prefixAddress; + event.prefixLength = prefixLength; + return event; + } + + @Test + public void testStackedLinkProperties() throws Exception { + final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24"); + final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64"); + final String kNat64PrefixString = "2001:db8:64:64:64:64::"; + final IpPrefix kNat64Prefix = new IpPrefix(InetAddress.getByName(kNat64PrefixString), 96); + final String kOtherNat64PrefixString = "64:ff9b::"; + final IpPrefix kOtherNat64Prefix = new IpPrefix( + InetAddress.getByName(kOtherNat64PrefixString), 96); + final RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, myIpv6.getAddress(), + MOBILE_IFNAME); + final RouteInfo ipv6Subnet = new RouteInfo(myIpv6, null, MOBILE_IFNAME); + final RouteInfo ipv4Subnet = new RouteInfo(myIpv4, null, MOBILE_IFNAME); + final RouteInfo stackedDefault = new RouteInfo((IpPrefix) null, myIpv4.getAddress(), + CLAT_PREFIX + MOBILE_IFNAME); + + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(networkRequest, networkCallback); + + // Prepare ipv6 only link properties. + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName(MOBILE_IFNAME); + cellLp.addLinkAddress(myIpv6); + cellLp.addRoute(defaultRoute); + cellLp.addRoute(ipv6Subnet); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + reset(mMockDnsResolver); + reset(mMockNetd); + + // Connect with ipv6 link properties. Expect prefix discovery to be started. + mCellNetworkAgent.connect(true); + final int cellNetId = mCellNetworkAgent.getNetwork().netId; + waitForIdle(); + + verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(cellNetId, + INetd.PERMISSION_NONE)); + assertRoutesAdded(cellNetId, ipv6Subnet, defaultRoute); + verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId)); + verify(mMockNetd, times(1)).networkAddInterface(cellNetId, MOBILE_IFNAME); + verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext, + cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); + + networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); + + // Switching default network updates TCP buffer sizes. + verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); + // Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that + // the NAT64 prefix was removed because one was never discovered. + cellLp.addLinkAddress(myIpv4); + mCellNetworkAgent.sendLinkProperties(cellLp); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + assertRoutesAdded(cellNetId, ipv4Subnet); + verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); + verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any()); + + // Make sure BatteryStats was not told about any v4- interfaces, as none should have + // come online yet. + waitForIdle(); + verify(mDeps, never()) + .reportNetworkInterfaceForTransports(eq(mServiceContext), startsWith("v4-"), any()); + + verifyNoMoreInteractions(mMockNetd); + verifyNoMoreInteractions(mMockDnsResolver); + reset(mMockNetd); + reset(mMockDnsResolver); + when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME)) + .thenReturn(getClatInterfaceConfigParcel(myIpv4)); + + // Remove IPv4 address. Expect prefix discovery to be started again. + cellLp.removeLinkAddress(myIpv4); + mCellNetworkAgent.sendLinkProperties(cellLp); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); + assertRoutesRemoved(cellNetId, ipv4Subnet); + + // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started. + Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent); + assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix()); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96)); + LinkProperties lpBeforeClat = networkCallback.expectCallback( + CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp(); + assertEquals(0, lpBeforeClat.getStackedLinks().size()); + assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix()); + verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); + + // Clat iface comes up. Expect stacked link to be added. + clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + List stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()) + .getStackedLinks(); + assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0)); + assertRoutesAdded(cellNetId, stackedDefault); + verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); + // Change trivial linkproperties and see if stacked link is preserved. + cellLp.addDnsServer(InetAddress.getByName("8.8.8.8")); + mCellNetworkAgent.sendLinkProperties(cellLp); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + + List stackedLpsAfterChange = + mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getStackedLinks(); + assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST); + assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0)); + + verify(mMockDnsResolver, times(1)).setResolverConfiguration( + mResolverParamsParcelCaptor.capture()); + ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue(); + assertEquals(1, resolvrParams.servers.length); + assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8")); + + for (final LinkProperties stackedLp : stackedLpsAfterChange) { + verify(mDeps).reportNetworkInterfaceForTransports( + mServiceContext, stackedLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); + } + reset(mMockNetd); + when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME)) + .thenReturn(getClatInterfaceConfigParcel(myIpv4)); + // Change the NAT64 prefix without first removing it. + // Expect clatd to be stopped and started with the new prefix. + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_ADDED, kOtherNat64PrefixString, 96)); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 0); + verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); + assertRoutesRemoved(cellNetId, stackedDefault); + verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); + + verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kOtherNat64Prefix.toString()); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getNat64Prefix().equals(kOtherNat64Prefix)); + clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 1); + assertRoutesAdded(cellNetId, stackedDefault); + verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); + reset(mMockNetd); + + // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked + // linkproperties are cleaned up. + cellLp.addLinkAddress(myIpv4); + cellLp.addRoute(ipv4Subnet); + mCellNetworkAgent.sendLinkProperties(cellLp); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + assertRoutesAdded(cellNetId, ipv4Subnet); + verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); + verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); + + // As soon as stop is called, the linkproperties lose the stacked interface. + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()); + LinkProperties expected = new LinkProperties(cellLp); + expected.setNat64Prefix(kOtherNat64Prefix); + assertEquals(expected, actualLpAfterIpv4); + assertEquals(0, actualLpAfterIpv4.getStackedLinks().size()); + assertRoutesRemoved(cellNetId, stackedDefault); + + // The interface removed callback happens but has no effect after stop is called. + clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME); + networkCallback.assertNoCallback(); + verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); + verifyNoMoreInteractions(mMockNetd); + verifyNoMoreInteractions(mMockDnsResolver); + reset(mMockNetd); + reset(mMockDnsResolver); + when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME)) + .thenReturn(getClatInterfaceConfigParcel(myIpv4)); + + // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone. + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_REMOVED, kOtherNat64PrefixString, 96)); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getNat64Prefix() == null); + + // Remove IPv4 address and expect prefix discovery and clatd to be started again. + cellLp.removeLinkAddress(myIpv4); + cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); + cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8")); + mCellNetworkAgent.sendLinkProperties(cellLp); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + assertRoutesRemoved(cellNetId, ipv4Subnet); // Directly-connected routes auto-added. + verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96)); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); + + // Clat iface comes up. Expect stacked link to be added. + clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null); + assertRoutesAdded(cellNetId, stackedDefault); + verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); + + // NAT64 prefix is removed. Expect that clat is stopped. + mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( + cellNetId, PREFIX_OPERATION_REMOVED, kNat64PrefixString, 96)); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null); + assertRoutesRemoved(cellNetId, ipv4Subnet, stackedDefault); + + // Stop has no effect because clat is already stopped. + verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + (lp) -> lp.getStackedLinks().size() == 0); + verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_PREFIX + MOBILE_IFNAME); + verify(mMockNetd, times(1)).interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME); + verifyNoMoreInteractions(mMockNetd); + // Clean up. + mCellNetworkAgent.disconnect(); + networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + networkCallback.assertNoCallback(); + mCm.unregisterNetworkCallback(networkCallback); + } + + private void expectNat64PrefixChange(TestableNetworkCallback callback, + TestNetworkAgentWrapper agent, IpPrefix prefix) { + callback.expectLinkPropertiesThat(agent, x -> Objects.equals(x.getNat64Prefix(), prefix)); + } + + @Test + public void testNat64PrefixMultipleSources() throws Exception { + final String iface = "wlan0"; + final String pref64FromRaStr = "64:ff9b::"; + final String pref64FromDnsStr = "2001:db8:64::"; + final IpPrefix pref64FromRa = new IpPrefix(InetAddress.getByName(pref64FromRaStr), 96); + final IpPrefix pref64FromDns = new IpPrefix(InetAddress.getByName(pref64FromDnsStr), 96); + final IpPrefix newPref64FromRa = new IpPrefix("2001:db8:64:64:64:64::/96"); + + final NetworkRequest request = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, callback); + + final LinkProperties baseLp = new LinkProperties(); + baseLp.setInterfaceName(iface); + baseLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); + baseLp.addDnsServer(InetAddress.getByName("2001:4860:4860::6464")); + + reset(mMockNetd, mMockDnsResolver); + InOrder inOrder = inOrder(mMockNetd, mMockDnsResolver); + + // If a network already has a NAT64 prefix on connect, clatd is started immediately and + // prefix discovery is never started. + LinkProperties lp = new LinkProperties(baseLp); + lp.setNat64Prefix(pref64FromRa); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp); + mWiFiNetworkAgent.connect(false); + final Network network = mWiFiNetworkAgent.getNetwork(); + int netId = network.getNetId(); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + callback.assertNoCallback(); + assertEquals(pref64FromRa, mCm.getLinkProperties(network).getNat64Prefix()); + + // If the RA prefix is withdrawn, clatd is stopped and prefix discovery is started. + lp.setNat64Prefix(null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); + inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); + + // If the RA prefix appears while DNS discovery is in progress, discovery is stopped and + // clatd is started with the prefix from the RA. + lp.setNat64Prefix(pref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa); + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); + + // Withdraw the RA prefix so we can test the case where an RA prefix appears after DNS + // discovery has succeeded. + lp.setNat64Prefix(null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); + inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); + + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96)); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); + + // If an RA advertises the same prefix that was discovered by DNS, nothing happens: prefix + // discovery is not stopped, and there are no callbacks. + lp.setNat64Prefix(pref64FromDns); + mWiFiNetworkAgent.sendLinkProperties(lp); + callback.assertNoCallback(); + inOrder.verify(mMockNetd, never()).clatdStop(iface); + inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + // If the RA is later withdrawn, nothing happens again. + lp.setNat64Prefix(null); + mWiFiNetworkAgent.sendLinkProperties(lp); + callback.assertNoCallback(); + inOrder.verify(mMockNetd, never()).clatdStop(iface); + inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + // If the RA prefix changes, clatd is restarted and prefix discovery is stopped. + lp.setNat64Prefix(pref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); + + // Stopping prefix discovery results in a prefix removed notification. + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_REMOVED, pref64FromDnsStr, 96)); + + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + + // If the RA prefix changes, clatd is restarted and prefix discovery is not started. + lp.setNat64Prefix(newPref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, newPref64FromRa); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); + inOrder.verify(mMockNetd).clatdStart(iface, newPref64FromRa.toString()); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, newPref64FromRa.toString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + + // If the RA prefix changes to the same value, nothing happens. + lp.setNat64Prefix(newPref64FromRa); + mWiFiNetworkAgent.sendLinkProperties(lp); + callback.assertNoCallback(); + assertEquals(newPref64FromRa, mCm.getLinkProperties(network).getNat64Prefix()); + inOrder.verify(mMockNetd, never()).clatdStop(iface); + inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + // The transition between no prefix and DNS prefix is tested in testStackedLinkProperties. + + // If the same prefix is learned first by DNS and then by RA, and clat is later stopped, + // (e.g., because the network disconnects) setPrefix64(netid, "") is never called. + lp.setNat64Prefix(null); + mWiFiNetworkAgent.sendLinkProperties(lp); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); + inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96)); + expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); + inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any()); + + lp.setNat64Prefix(pref64FromDns); + mWiFiNetworkAgent.sendLinkProperties(lp); + callback.assertNoCallback(); + inOrder.verify(mMockNetd, never()).clatdStop(iface); + inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); + inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + // When tearing down a network, clat state is only updated after CALLBACK_LOST is fired, but + // before CONNECTIVITY_ACTION is sent. Wait for CONNECTIVITY_ACTION before verifying that + // clat has been stopped, or the test will be flaky. + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + b.expectBroadcast(); + + inOrder.verify(mMockNetd).clatdStop(iface); + inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); + inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); + + mCm.unregisterNetworkCallback(callback); + } + + @Test + public void testWith464XlatDisable() throws Exception { + doReturn(false).when(mDeps).getCellular464XlatEnabled(); + + final TestNetworkCallback callback = new TestNetworkCallback(); + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + mCm.registerNetworkCallback(networkRequest, callback); + mCm.registerDefaultNetworkCallback(defaultCallback); + + // Bring up validated cell. + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName(MOBILE_IFNAME); + cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); + cellLp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, MOBILE_IFNAME)); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + + mCellNetworkAgent.sendLinkProperties(cellLp); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + final int cellNetId = mCellNetworkAgent.getNetwork().netId; + waitForIdle(); + + verify(mMockDnsResolver, never()).startPrefix64Discovery(cellNetId); + Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent); + assertTrue("Nat464Xlat was not IDLE", !clat.isStarted()); + + // This cannot happen because prefix discovery cannot succeed if it is never started. + mService.mResolverUnsolEventCallback.onNat64PrefixEvent( + makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, "64:ff9b::", 96)); + + // ... but still, check that even if it did, clatd would not be started. + verify(mMockNetd, never()).clatdStart(anyString(), anyString()); + assertTrue("Nat464Xlat was not IDLE", !clat.isStarted()); + } + + @Test + public void testDataActivityTracking() throws Exception { + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + mCm.registerNetworkCallback(networkRequest, networkCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName(MOBILE_IFNAME); + mCellNetworkAgent.sendLinkProperties(cellLp); + mCellNetworkAgent.connect(true); + networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_CELLULAR))); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName(WIFI_IFNAME); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + + // Network switch + mWiFiNetworkAgent.connect(true); + networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_WIFI))); + verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_CELLULAR))); + + // Disconnect wifi and switch back to cell + reset(mMockNetd); + mWiFiNetworkAgent.disconnect(); + networkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + assertNoCallbacks(networkCallback); + verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_WIFI))); + verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_CELLULAR))); + + // reconnect wifi + reset(mMockNetd); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + wifiLp.setInterfaceName(WIFI_IFNAME); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + mWiFiNetworkAgent.connect(true); + networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_WIFI))); + verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_CELLULAR))); + + // Disconnect cell + reset(mMockNetd); + mCellNetworkAgent.disconnect(); + networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + // LOST callback is triggered earlier than removing idle timer. Broadcast should also be + // sent as network being switched. Ensure rule removal for cell will not be triggered + // unexpectedly before network being removed. + waitForIdle(); + verify(mMockNetd, times(0)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_CELLULAR))); + verify(mMockNetd, times(1)).networkDestroy(eq(mCellNetworkAgent.getNetwork().netId)); + verify(mMockDnsResolver, times(1)) + .destroyNetworkCache(eq(mCellNetworkAgent.getNetwork().netId)); + + // Disconnect wifi + ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); + mWiFiNetworkAgent.disconnect(); + b.expectBroadcast(); + verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(), + eq(Integer.toString(TRANSPORT_WIFI))); + + // Clean up + mCm.unregisterNetworkCallback(networkCallback); + } + + private void verifyTcpBufferSizeChange(String tcpBufferSizes) throws Exception { + String[] values = tcpBufferSizes.split(","); + String rmemValues = String.join(" ", values[0], values[1], values[2]); + String wmemValues = String.join(" ", values[3], values[4], values[5]); + verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues); + reset(mMockNetd); + } + + @Test + public void testTcpBufferReset() throws Exception { + final String testTcpBufferSizes = "1,2,3,4,5,6"; + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(networkRequest, networkCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + reset(mMockNetd); + // Switching default network updates TCP buffer sizes. + mCellNetworkAgent.connect(false); + networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); + // Change link Properties should have updated tcp buffer size. + LinkProperties lp = new LinkProperties(); + lp.setTcpBufferSizes(testTcpBufferSizes); + mCellNetworkAgent.sendLinkProperties(lp); + networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + verifyTcpBufferSizeChange(testTcpBufferSizes); + // Clean up. + mCellNetworkAgent.disconnect(); + networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + networkCallback.assertNoCallback(); + mCm.unregisterNetworkCallback(networkCallback); + } + + @Test + public void testGetGlobalProxyForNetwork() throws Exception { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + final Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); + when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo); + assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork)); + } + + @Test + public void testGetProxyForActiveNetwork() throws Exception { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + final LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } + + @Test + public void testGetProxyForVPN() throws Exception { + final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); + + // Set up a WiFi network with no proxy + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Connect a VPN network with a proxy. + LinkProperties testLinkProperties = new LinkProperties(); + testLinkProperties.setHttpProxy(testProxyInfo); + mMockVpn.establishForMyUid(testLinkProperties); + assertUidRangesUpdatedForMyUid(true); + + // Test that the VPN network returns a proxy, and the WiFi does not. + assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + + // Test that the VPN network returns no proxy when it is set to null. + testLinkProperties.setHttpProxy(null); + mMockVpn.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(mMockVpn.getNetwork())); + assertNull(mService.getProxyForNetwork(null)); + + // Set WiFi proxy and check that the vpn proxy is still null. + testLinkProperties.setHttpProxy(testProxyInfo); + mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); + waitForIdle(); + assertNull(mService.getProxyForNetwork(null)); + + // Disconnect from VPN and check that the active network, which is now the WiFi, has the + // correct proxy setting. + mMockVpn.disconnect(); + waitForIdle(); + assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); + assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); + } + + @Test + public void testFullyRoutedVpnResultsInInterfaceFilteringRules() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); + // The uid range needs to cover the test app so the network is visible to it. + final Set vpnRange = Collections.singleton(PRIMARY_UIDRANGE); + mMockVpn.establish(lp, VPN_UID, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, VPN_UID); + + // A connected VPN should have interface rules set up. There are two expected invocations, + // one during the VPN initial connection, one during the VPN LinkProperties update. + ArgumentCaptor uidCaptor = ArgumentCaptor.forClass(int[].class); + verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture()); + assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID); + assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID); + assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange)); + + mMockVpn.disconnect(); + waitForIdle(); + + // Disconnected VPN should have interface rules removed + verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); + assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); + assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0")); + } + + @Test + public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + // The uid range needs to cover the test app so the network is visible to it. + final Set vpnRange = Collections.singleton(PRIMARY_UIDRANGE); + mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID); + + // Legacy VPN should not have interface rules set up + verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); + } + + @Test + public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule() + throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0")); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); + // The uid range needs to cover the test app so the network is visible to it. + final Set vpnRange = Collections.singleton(PRIMARY_UIDRANGE); + mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID); + + // IPv6 unreachable route should not be misinterpreted as a default route + verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); + } + + @Test + public void testVpnHandoverChangesInterfaceFilteringRule() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); + // The uid range needs to cover the test app so the network is visible to it. + final Set vpnRange = Collections.singleton(PRIMARY_UIDRANGE); + mMockVpn.establish(lp, VPN_UID, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, VPN_UID); + + // Connected VPN should have interface rules set up. There are two expected invocations, + // one during VPN uid update, one during VPN LinkProperties update + ArgumentCaptor uidCaptor = ArgumentCaptor.forClass(int[].class); + verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture()); + assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID); + assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID); + + reset(mMockNetd); + InOrder inOrder = inOrder(mMockNetd); + lp.setInterfaceName("tun1"); + mMockVpn.sendLinkProperties(lp); + waitForIdle(); + // VPN handover (switch to a new interface) should result in rules being updated (old rules + // removed first, then new rules added) + inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); + assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); + inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture()); + assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); + + reset(mMockNetd); + lp = new LinkProperties(); + lp.setInterfaceName("tun1"); + lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1")); + mMockVpn.sendLinkProperties(lp); + waitForIdle(); + // VPN not routing everything should no longer have interface filtering rules + verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); + assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); + + reset(mMockNetd); + lp = new LinkProperties(); + lp.setInterfaceName("tun1"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); + mMockVpn.sendLinkProperties(lp); + waitForIdle(); + // Back to routing all IPv6 traffic should have filtering rules + verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture()); + assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); + } + + @Test + public void testUidUpdateChangesInterfaceFilteringRule() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); + // The uid range needs to cover the test app so the network is visible to it. + final UidRange vpnRange = PRIMARY_UIDRANGE; + final Set vpnRanges = Collections.singleton(vpnRange); + mMockVpn.establish(lp, VPN_UID, vpnRanges); + assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); + + reset(mMockNetd); + InOrder inOrder = inOrder(mMockNetd); + + // Update to new range which is old range minus APP1, i.e. only APP2 + final Set newRanges = new HashSet<>(Arrays.asList( + new UidRange(vpnRange.start, APP1_UID - 1), + new UidRange(APP1_UID + 1, vpnRange.stop))); + mMockVpn.setUids(newRanges); + waitForIdle(); + + ArgumentCaptor uidCaptor = ArgumentCaptor.forClass(int[].class); + // Verify old rules are removed before new rules are added + inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); + assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); + inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture()); + assertContainsExactly(uidCaptor.getValue(), APP2_UID); + } + + @Test + public void testLinkPropertiesWithWakeOnLanForActiveNetwork() throws Exception { + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + + LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName(WIFI_WOL_IFNAME); + wifiLp.setWakeOnLanSupported(false); + + // Default network switch should update ifaces. + mWiFiNetworkAgent.connect(false); + mWiFiNetworkAgent.sendLinkProperties(wifiLp); + waitForIdle(); + + // ConnectivityService should have changed the WakeOnLanSupported to true + wifiLp.setWakeOnLanSupported(true); + assertEquals(wifiLp, mService.getActiveLinkProperties()); + } + + @Test + public void testLegacyExtraInfoSentToNetworkMonitor() throws Exception { + class TestNetworkAgent extends NetworkAgent { + TestNetworkAgent(Context context, Looper looper, NetworkAgentConfig config) { + super(context, looper, "MockAgent", new NetworkCapabilities(), + new LinkProperties(), 40 , config, null /* provider */); + } + } + final NetworkAgent naNoExtraInfo = new TestNetworkAgent( + mServiceContext, mCsHandlerThread.getLooper(), new NetworkAgentConfig()); + naNoExtraInfo.register(); + verify(mNetworkStack).makeNetworkMonitor(any(), isNull(String.class), any()); + naNoExtraInfo.unregister(); + + reset(mNetworkStack); + final NetworkAgentConfig config = + new NetworkAgentConfig.Builder().setLegacyExtraInfo("legacyinfo").build(); + final NetworkAgent naExtraInfo = new TestNetworkAgent( + mServiceContext, mCsHandlerThread.getLooper(), config); + naExtraInfo.register(); + verify(mNetworkStack).makeNetworkMonitor(any(), eq("legacyinfo"), any()); + naExtraInfo.unregister(); + } + + // To avoid granting location permission bypass. + private void denyAllLocationPrivilegedPermissions() { + mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + PERMISSION_DENIED); + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); + mServiceContext.setPermission(Manifest.permission.NETWORK_STACK, + PERMISSION_DENIED); + mServiceContext.setPermission(Manifest.permission.NETWORK_SETUP_WIZARD, + PERMISSION_DENIED); + } + + private void setupLocationPermissions( + int targetSdk, boolean locationToggle, String op, String perm) throws Exception { + denyAllLocationPrivilegedPermissions(); + + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.targetSdkVersion = targetSdk; + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) + .thenReturn(applicationInfo); + when(mPackageManager.getTargetSdkVersion(any())).thenReturn(targetSdk); + + when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle); + + if (op != null) { + when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), + eq(mContext.getPackageName()), eq(getAttributionTag()), anyString())) + .thenReturn(AppOpsManager.MODE_ALLOWED); + } + + if (perm != null) { + mServiceContext.setPermission(perm, PERMISSION_GRANTED); + } + } + + private int getOwnerUidNetCapsPermission(int ownerUid, int callerUid, + boolean includeLocationSensitiveInfo) { + final NetworkCapabilities netCap = new NetworkCapabilities().setOwnerUid(ownerUid); + + return mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( + netCap, includeLocationSensitiveInfo, Process.myUid(), callerUid, + mContext.getPackageName(), getAttributionTag()) + .getOwnerUid(); + } + + private void verifyTransportInfoCopyNetCapsPermission( + int callerUid, boolean includeLocationSensitiveInfo, + boolean shouldMakeCopyWithLocationSensitiveFieldsParcelable) { + final TransportInfo transportInfo = mock(TransportInfo.class); + when(transportInfo.getApplicableRedactions()).thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION); + final NetworkCapabilities netCap = + new NetworkCapabilities().setTransportInfo(transportInfo); + + mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( + netCap, includeLocationSensitiveInfo, Process.myPid(), callerUid, + mContext.getPackageName(), getAttributionTag()); + if (shouldMakeCopyWithLocationSensitiveFieldsParcelable) { + verify(transportInfo).makeCopy(REDACT_NONE); + } else { + verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION); + } + } + + private void verifyOwnerUidAndTransportInfoNetCapsPermission( + boolean shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag, + boolean shouldInclLocationSensitiveOwnerUidWithIncludeFlag, + boolean shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag, + boolean shouldInclLocationSensitiveTransportInfoWithIncludeFlag) { + final int myUid = Process.myUid(); + + final int expectedOwnerUidWithoutIncludeFlag = + shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag + ? myUid : INVALID_UID; + assertEquals(expectedOwnerUidWithoutIncludeFlag, getOwnerUidNetCapsPermission( + myUid, myUid, false /* includeLocationSensitiveInfo */)); + + final int expectedOwnerUidWithIncludeFlag = + shouldInclLocationSensitiveOwnerUidWithIncludeFlag ? myUid : INVALID_UID; + assertEquals(expectedOwnerUidWithIncludeFlag, getOwnerUidNetCapsPermission( + myUid, myUid, true /* includeLocationSensitiveInfo */)); + + verifyTransportInfoCopyNetCapsPermission(myUid, + false, /* includeLocationSensitiveInfo */ + shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag); + + verifyTransportInfoCopyNetCapsPermission(myUid, + true, /* includeLocationSensitiveInfo */ + shouldInclLocationSensitiveTransportInfoWithIncludeFlag); + + } + + private void verifyOwnerUidAndTransportInfoNetCapsPermissionPreS() { + verifyOwnerUidAndTransportInfoNetCapsPermission( + // Ensure that owner uid is included even if the request asks to remove it (which is + // the default) since the app has necessary permissions and targetSdk < S. + true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + // Ensure that location info is removed if the request asks to remove it even if the + // app has necessary permissions. + false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */ + true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */ + ); + } + + @Test + public void testCreateWithLocationInfoSanitizedWithFineLocationAfterQPreS() + throws Exception { + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + verifyOwnerUidAndTransportInfoNetCapsPermissionPreS(); + } + + @Test + public void testCreateWithLocationInfoSanitizedWithFineLocationPreSWithAndWithoutCallbackFlag() + throws Exception { + setupLocationPermissions(Build.VERSION_CODES.R, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + verifyOwnerUidAndTransportInfoNetCapsPermissionPreS(); + } + + @Test + public void + testCreateWithLocationInfoSanitizedWithFineLocationAfterSWithAndWithoutCallbackFlag() + throws Exception { + setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + verifyOwnerUidAndTransportInfoNetCapsPermission( + // Ensure that the owner UID is removed if the request asks us to remove it even + // if the app has necessary permissions since targetSdk >= S. + false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + // Ensure that location info is removed if the request asks to remove it even if the + // app has necessary permissions. + false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */ + true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */ + ); + } + + @Test + public void testCreateWithLocationInfoSanitizedWithCoarseLocationPreQ() + throws Exception { + setupLocationPermissions(Build.VERSION_CODES.P, true, AppOpsManager.OPSTR_COARSE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION); + + verifyOwnerUidAndTransportInfoNetCapsPermissionPreS(); + } + + private void verifyOwnerUidAndTransportInfoNetCapsNotIncluded() { + verifyOwnerUidAndTransportInfoNetCapsPermission( + false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ + false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ + false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */ + false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */ + ); + } + + @Test + public void testCreateWithLocationInfoSanitizedLocationOff() throws Exception { + // Test that even with fine location permission, and UIDs matching, the UID is sanitized. + setupLocationPermissions(Build.VERSION_CODES.Q, false, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + verifyOwnerUidAndTransportInfoNetCapsNotIncluded(); + } + + @Test + public void testCreateWithLocationInfoSanitizedWrongUid() throws Exception { + // Test that even with fine location permission, not being the owner leads to sanitization. + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + final int myUid = Process.myUid(); + assertEquals(Process.INVALID_UID, + getOwnerUidNetCapsPermission(myUid + 1, myUid, + true /* includeLocationSensitiveInfo */)); + } + + @Test + public void testCreateWithLocationInfoSanitizedWithCoarseLocationAfterQ() + throws Exception { + // Test that not having fine location permission leads to sanitization. + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_COARSE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION); + + verifyOwnerUidAndTransportInfoNetCapsNotIncluded(); + } + + @Test + public void testCreateWithLocationInfoSanitizedWithCoarseLocationAfterS() + throws Exception { + // Test that not having fine location permission leads to sanitization. + setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_COARSE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION); + + verifyOwnerUidAndTransportInfoNetCapsNotIncluded(); + } + + @Test + public void testCreateForCallerWithLocalMacAddressSanitizedWithLocalMacAddressPermission() + throws Exception { + mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_GRANTED); + + final TransportInfo transportInfo = mock(TransportInfo.class); + when(transportInfo.getApplicableRedactions()) + .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS); + final NetworkCapabilities netCap = + new NetworkCapabilities().setTransportInfo(transportInfo); + + mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( + netCap, false /* includeLocationSensitiveInfoInTransportInfo */, + Process.myPid(), Process.myUid(), + mContext.getPackageName(), getAttributionTag()); + // don't redact MAC_ADDRESS fields, only location sensitive fields. + verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION); + } + + @Test + public void testCreateForCallerWithLocalMacAddressSanitizedWithoutLocalMacAddressPermission() + throws Exception { + mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_DENIED); + + final TransportInfo transportInfo = mock(TransportInfo.class); + when(transportInfo.getApplicableRedactions()) + .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS); + final NetworkCapabilities netCap = + new NetworkCapabilities().setTransportInfo(transportInfo); + + mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( + netCap, false /* includeLocationSensitiveInfoInTransportInfo */, + Process.myPid(), Process.myUid(), + mContext.getPackageName(), getAttributionTag()); + // redact both MAC_ADDRESS & location sensitive fields. + verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION + | REDACT_FOR_LOCAL_MAC_ADDRESS); + } + + @Test + public void testCreateForCallerWithLocalMacAddressSanitizedWithSettingsPermission() + throws Exception { + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + + final TransportInfo transportInfo = mock(TransportInfo.class); + when(transportInfo.getApplicableRedactions()) + .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS); + final NetworkCapabilities netCap = + new NetworkCapabilities().setTransportInfo(transportInfo); + + mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( + netCap, false /* includeLocationSensitiveInfoInTransportInfo */, + Process.myPid(), Process.myUid(), + mContext.getPackageName(), getAttributionTag()); + // don't redact NETWORK_SETTINGS fields, only location sensitive fields. + verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION); + } + + @Test + public void testCreateForCallerWithLocalMacAddressSanitizedWithoutSettingsPermission() + throws Exception { + mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_DENIED); + + final TransportInfo transportInfo = mock(TransportInfo.class); + when(transportInfo.getApplicableRedactions()) + .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS); + final NetworkCapabilities netCap = + new NetworkCapabilities().setTransportInfo(transportInfo); + + mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( + netCap, false /* includeLocationSensitiveInfoInTransportInfo */, + Process.myPid(), Process.myUid(), + mContext.getPackageName(), getAttributionTag()); + // redact both NETWORK_SETTINGS & location sensitive fields. + verify(transportInfo).makeCopy( + REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS); + } + + /** + * Test TransportInfo to verify redaction mechanism. + */ + private static class TestTransportInfo implements TransportInfo { + public final boolean locationRedacted; + public final boolean localMacAddressRedacted; + public final boolean settingsRedacted; + + TestTransportInfo() { + locationRedacted = false; + localMacAddressRedacted = false; + settingsRedacted = false; + } + + TestTransportInfo(boolean locationRedacted, boolean localMacAddressRedacted, + boolean settingsRedacted) { + this.locationRedacted = locationRedacted; + this.localMacAddressRedacted = + localMacAddressRedacted; + this.settingsRedacted = settingsRedacted; + } + + @Override + public TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) { + return new TestTransportInfo( + (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0, + (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0, + (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0 + ); + } + + @Override + public @NetworkCapabilities.RedactionType long getApplicableRedactions() { + return REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS + | REDACT_FOR_NETWORK_SETTINGS; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof TestTransportInfo)) return false; + TestTransportInfo that = (TestTransportInfo) other; + return that.locationRedacted == this.locationRedacted + && that.localMacAddressRedacted == this.localMacAddressRedacted + && that.settingsRedacted == this.settingsRedacted; + } + + @Override + public int hashCode() { + return Objects.hash(locationRedacted, localMacAddressRedacted, settingsRedacted); + } + } + + private void verifyNetworkCallbackLocationDataInclusionUsingTransportInfoAndOwnerUidInNetCaps( + @NonNull TestNetworkCallback wifiNetworkCallback, int actualOwnerUid, + @NonNull TransportInfo actualTransportInfo, int expectedOwnerUid, + @NonNull TransportInfo expectedTransportInfo) throws Exception { + when(mPackageManager.getTargetSdkVersion(anyString())).thenReturn(Build.VERSION_CODES.S); + final NetworkCapabilities ncTemplate = + new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .setOwnerUid(actualOwnerUid); + + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), + ncTemplate); + mWiFiNetworkAgent.connect(false); + + wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + + // Send network capabilities update with TransportInfo to trigger capabilities changed + // callback. + mWiFiNetworkAgent.setNetworkCapabilities( + ncTemplate.setTransportInfo(actualTransportInfo), true); + + wifiNetworkCallback.expectCapabilitiesThat(mWiFiNetworkAgent, + nc -> Objects.equals(expectedOwnerUid, nc.getOwnerUid()) + && Objects.equals(expectedTransportInfo, nc.getTransportInfo())); + + } + + @Test + public void testVerifyLocationDataIsNotIncludedWhenInclFlagNotSet() throws Exception { + final TestNetworkCallback wifiNetworkCallack = new TestNetworkCallback(); + final int ownerUid = Process.myUid(); + final TransportInfo transportInfo = new TestTransportInfo(); + // Even though the test uid holds privileged permissions, mask location fields since + // the callback did not explicitly opt-in to get location data. + final TransportInfo sanitizedTransportInfo = new TestTransportInfo( + true, /* locationRedacted */ + true, /* localMacAddressRedacted */ + true /* settingsRedacted */ + ); + // Should not expect location data since the callback does not set the flag for including + // location data. + verifyNetworkCallbackLocationDataInclusionUsingTransportInfoAndOwnerUidInNetCaps( + wifiNetworkCallack, ownerUid, transportInfo, INVALID_UID, sanitizedTransportInfo); + } + + private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) + throws Exception { + final Set vpnRange = Collections.singleton(PRIMARY_UIDRANGE); + mMockVpn.setVpnType(vpnType); + mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange); + assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid); + + final UnderlyingNetworkInfo underlyingNetworkInfo = + new UnderlyingNetworkInfo(vpnOwnerUid, VPN_IFNAME, new ArrayList()); + mMockVpn.setUnderlyingNetworkInfo(underlyingNetworkInfo); + when(mDeps.getConnectionOwnerUid(anyInt(), any(), any())).thenReturn(42); + } + + private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType) + throws Exception { + setupConnectionOwnerUid(vpnOwnerUid, vpnType); + + // Test as VPN app + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + mServiceContext.setPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED); + } + + private ConnectionInfo getTestConnectionInfo() throws Exception { + return new ConnectionInfo( + IPPROTO_TCP, + new InetSocketAddress(InetAddresses.parseNumericAddress("1.2.3.4"), 1234), + new InetSocketAddress(InetAddresses.parseNumericAddress("2.3.4.5"), 2345)); + } + + @Test + public void testGetConnectionOwnerUidPlatformVpn() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM); + + assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo())); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceWrongUser() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE); + + assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo())); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceDoesNotThrow() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE); + + assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceNetworkStackDoesNotThrow() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceMainlineNetworkStackDoesNotThrow() + throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); + mServiceContext.setPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED); + + assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); + } + + private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) { + final PackageInfo packageInfo = new PackageInfo(); + if (hasSystemPermission) { + packageInfo.requestedPermissions = new String[] { + CHANGE_NETWORK_STATE, CONNECTIVITY_USE_RESTRICTED_NETWORKS }; + packageInfo.requestedPermissionsFlags = new int[] { + REQUESTED_PERMISSION_GRANTED, REQUESTED_PERMISSION_GRANTED }; + } else { + packageInfo.requestedPermissions = new String[0]; + } + packageInfo.applicationInfo = new ApplicationInfo(); + packageInfo.applicationInfo.privateFlags = 0; + packageInfo.applicationInfo.uid = UserHandle.getUid(UserHandle.USER_SYSTEM, + UserHandle.getAppId(uid)); + return packageInfo; + } + + @Test + public void testRegisterConnectivityDiagnosticsCallbackInvalidRequest() throws Exception { + final NetworkRequest request = + new NetworkRequest( + new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE); + try { + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest"); + } catch (IllegalArgumentException expected) { + } + } + + private void assertRouteInfoParcelMatches(RouteInfo route, RouteInfoParcel parcel) { + assertEquals(route.getDestination().toString(), parcel.destination); + assertEquals(route.getInterface(), parcel.ifName); + assertEquals(route.getMtu(), parcel.mtu); + + switch (route.getType()) { + case RouteInfo.RTN_UNICAST: + if (route.hasGateway()) { + assertEquals(route.getGateway().getHostAddress(), parcel.nextHop); + } else { + assertEquals(INetd.NEXTHOP_NONE, parcel.nextHop); + } + break; + case RouteInfo.RTN_UNREACHABLE: + assertEquals(INetd.NEXTHOP_UNREACHABLE, parcel.nextHop); + break; + case RouteInfo.RTN_THROW: + assertEquals(INetd.NEXTHOP_THROW, parcel.nextHop); + break; + default: + assertEquals(INetd.NEXTHOP_NONE, parcel.nextHop); + break; + } + } + + private void assertRoutesAdded(int netId, RouteInfo... routes) throws Exception { + ArgumentCaptor captor = ArgumentCaptor.forClass(RouteInfoParcel.class); + verify(mMockNetd, times(routes.length)).networkAddRouteParcel(eq(netId), captor.capture()); + for (int i = 0; i < routes.length; i++) { + assertRouteInfoParcelMatches(routes[i], captor.getAllValues().get(i)); + } + } + + private void assertRoutesRemoved(int netId, RouteInfo... routes) throws Exception { + ArgumentCaptor captor = ArgumentCaptor.forClass(RouteInfoParcel.class); + verify(mMockNetd, times(routes.length)).networkRemoveRouteParcel(eq(netId), + captor.capture()); + for (int i = 0; i < routes.length; i++) { + assertRouteInfoParcelMatches(routes[i], captor.getAllValues().get(i)); + } + } + + @Test + public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest wifiRequest = + new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + verify(mConnectivityDiagnosticsCallback).asBinder(); + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); + + mService.unregisterConnectivityDiagnosticsCallback(mConnectivityDiagnosticsCallback); + verify(mIBinder, timeout(TIMEOUT_MS)) + .unlinkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + assertFalse(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); + verify(mConnectivityDiagnosticsCallback, atLeastOnce()).asBinder(); + } + + @Test + public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest wifiRequest = + new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); + verify(mConnectivityDiagnosticsCallback).asBinder(); + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); + + // Register the same callback again + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); + } + + public NetworkAgentInfo fakeMobileNai(NetworkCapabilities nc) { + final NetworkInfo info = new NetworkInfo(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_LTE, + ConnectivityManager.getNetworkTypeName(TYPE_MOBILE), + TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE)); + return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(), + nc, new NetworkScore.Builder().setLegacyInt(0).build(), + mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0, + INVALID_UID, mQosCallbackTracker, new ConnectivityService.Dependencies()); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception { + final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities()); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + assertTrue( + "NetworkStack permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsWrongUidPackageName() throws Exception { + final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities()); + + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + assertFalse( + "Mismatched uid/package name should not pass the location permission check", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid() + 1, Process.myUid() + 1, naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception { + final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities()); + + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + assertFalse( + "ACCESS_FINE_LOCATION permission necessary for Connectivity Diagnostics", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception { + final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities()); + + mMockVpn.establishForMyUid(); + assertUidRangesUpdatedForMyUid(true); + + // Wait for networks to connect and broadcasts to be sent before removing permissions. + waitForIdle(); + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + + assertTrue(mMockVpn.setUnderlyingNetworks(new Network[] {naiWithoutUid.network})); + waitForIdle(); + assertTrue( + "Active VPN permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + + assertTrue(mMockVpn.setUnderlyingNetworks(null)); + waitForIdle(); + assertFalse( + "VPN shouldn't receive callback on non-underlying network", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNetworkAdministrator() throws Exception { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setAdministratorUids(new int[] {Process.myUid()}); + final NetworkAgentInfo naiWithUid = fakeMobileNai(nc); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + assertTrue( + "NetworkCapabilities administrator uid permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsFails() throws Exception { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setOwnerUid(Process.myUid()); + nc.setAdministratorUids(new int[] {Process.myUid()}); + final NetworkAgentInfo naiWithUid = fakeMobileNai(nc); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + // Use wrong pid and uid + assertFalse( + "Permissions allowed when they shouldn't be granted", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid() + 1, Process.myUid() + 1, naiWithUid, + mContext.getOpPackageName())); + } + + @Test + public void testRegisterConnectivityDiagnosticsCallbackCallsOnConnectivityReport() + throws Exception { + // Set up the Network, which leads to a ConnectivityReport being cached for the network. + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, linkProperties); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callback.assertNoCallback(); + + final NetworkRequest request = new NetworkRequest.Builder().build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + verify(mConnectivityDiagnosticsCallback) + .onConnectivityReportAvailable(argThat(report -> { + return INTERFACE_NAME.equals(report.getLinkProperties().getInterfaceName()) + && report.getNetworkCapabilities().hasTransport(TRANSPORT_CELLULAR); + })); + } + + private void setUpConnectivityDiagnosticsCallback() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Connect the cell agent verify that it notifies TestNetworkCallback that it is available + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callback.assertNoCallback(); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReportAvailable() + throws Exception { + setUpConnectivityDiagnosticsCallback(); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onConnectivityReport fired + verify(mConnectivityDiagnosticsCallback).onConnectivityReportAvailable( + argThat(report -> { + final NetworkCapabilities nc = report.getNetworkCapabilities(); + return nc.getUids() == null + && nc.getAdministratorUids().length == 0 + && nc.getOwnerUid() == Process.INVALID_UID; + })); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the + // cellular network agent + mCellNetworkAgent.notifyDataStallSuspected(); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onDataStallSuspected fired + verify(mConnectivityDiagnosticsCallback).onDataStallSuspected( + argThat(report -> { + final NetworkCapabilities nc = report.getNetworkCapabilities(); + return nc.getUids() == null + && nc.getAdministratorUids().length == 0 + && nc.getOwnerUid() == Process.INVALID_UID; + })); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception { + setUpConnectivityDiagnosticsCallback(); + + final Network n = mCellNetworkAgent.getNetwork(); + final boolean hasConnectivity = true; + mService.reportNetworkConnectivity(n, hasConnectivity); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Verify onNetworkConnectivityReported fired + verify(mConnectivityDiagnosticsCallback) + .onNetworkConnectivityReported(eq(n), eq(hasConnectivity)); + + final boolean noConnectivity = false; + mService.reportNetworkConnectivity(n, noConnectivity); + + // Block until all other events are done processing. + HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Wait for onNetworkConnectivityReported to fire + verify(mConnectivityDiagnosticsCallback) + .onNetworkConnectivityReported(eq(n), eq(noConnectivity)); + } + + @Test + public void testRouteAddDeleteUpdate() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(request, networkCallback); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + reset(mMockNetd); + mCellNetworkAgent.connect(false); + networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + final int netId = mCellNetworkAgent.getNetwork().netId; + + final String iface = "rmnet_data0"; + final InetAddress gateway = InetAddress.getByName("fe80::5678"); + RouteInfo direct = RouteInfo.makeHostRoute(gateway, iface); + RouteInfo rio1 = new RouteInfo(new IpPrefix("2001:db8:1::/48"), gateway, iface); + RouteInfo rio2 = new RouteInfo(new IpPrefix("2001:db8:2::/48"), gateway, iface); + RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, gateway, iface); + RouteInfo defaultWithMtu = new RouteInfo(null, gateway, iface, RouteInfo.RTN_UNICAST, + 1280 /* mtu */); + + // Send LinkProperties and check that we ask netd to add routes. + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(iface); + lp.addRoute(direct); + lp.addRoute(rio1); + lp.addRoute(defaultRoute); + mCellNetworkAgent.sendLinkProperties(lp); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, x -> x.getRoutes().size() == 3); + + assertRoutesAdded(netId, direct, rio1, defaultRoute); + reset(mMockNetd); + + // Send updated LinkProperties and check that we ask netd to add, remove, update routes. + assertTrue(lp.getRoutes().contains(defaultRoute)); + lp.removeRoute(rio1); + lp.addRoute(rio2); + lp.addRoute(defaultWithMtu); + // Ensure adding the same route with a different MTU replaces the previous route. + assertFalse(lp.getRoutes().contains(defaultRoute)); + assertTrue(lp.getRoutes().contains(defaultWithMtu)); + + mCellNetworkAgent.sendLinkProperties(lp); + networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, + x -> x.getRoutes().contains(rio2)); + + assertRoutesRemoved(netId, rio1); + assertRoutesAdded(netId, rio2); + + ArgumentCaptor captor = ArgumentCaptor.forClass(RouteInfoParcel.class); + verify(mMockNetd).networkUpdateRouteParcel(eq(netId), captor.capture()); + assertRouteInfoParcelMatches(defaultWithMtu, captor.getValue()); + + + mCm.unregisterNetworkCallback(networkCallback); + } + + @Test + public void testDumpDoesNotCrash() { + mServiceContext.setPermission(DUMP, PERMISSION_GRANTED); + // Filing a couple requests prior to testing the dump. + final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); + final NetworkRequest genericRequest = new NetworkRequest.Builder() + .clearCapabilities().build(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); + mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); + final StringWriter stringWriter = new StringWriter(); + + mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]); + + assertFalse(stringWriter.toString().isEmpty()); + } + + @Test + public void testRequestsSortedByIdSortsCorrectly() { + final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest genericRequest = new NetworkRequest.Builder() + .clearCapabilities().build(); + final NetworkRequest wifiRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR).build(); + mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); + mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); + mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); + waitForIdle(); + + final ConnectivityService.NetworkRequestInfo[] nriOutput = mService.requestsSortedById(); + + assertTrue(nriOutput.length > 1); + for (int i = 0; i < nriOutput.length - 1; i++) { + final boolean isRequestIdInOrder = + nriOutput[i].mRequests.get(0).requestId + < nriOutput[i + 1].mRequests.get(0).requestId; + assertTrue(isRequestIdInOrder); + } + } + + private void assertUidRangesUpdatedForMyUid(boolean add) throws Exception { + final int uid = Process.myUid(); + assertVpnUidRangesUpdated(add, uidRangesForUids(uid), uid); + } + + private void assertVpnUidRangesUpdated(boolean add, Set vpnRanges, int exemptUid) + throws Exception { + InOrder inOrder = inOrder(mMockNetd); + ArgumentCaptor exemptUidCaptor = ArgumentCaptor.forClass(int[].class); + + inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)), + exemptUidCaptor.capture()); + assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid); + + if (add) { + inOrder.verify(mMockNetd, times(1)) + .networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()), + eq(toUidRangeStableParcels(vpnRanges))); + } else { + inOrder.verify(mMockNetd, times(1)) + .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()), + eq(toUidRangeStableParcels(vpnRanges))); + } + + inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)), + exemptUidCaptor.capture()); + assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid); + } + + @Test + public void testVpnUidRangesUpdate() throws Exception { + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("tun0"); + lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); + lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); + final UidRange vpnRange = PRIMARY_UIDRANGE; + Set vpnRanges = Collections.singleton(vpnRange); + mMockVpn.establish(lp, VPN_UID, vpnRanges); + assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); + + reset(mMockNetd); + // Update to new range which is old range minus APP1, i.e. only APP2 + final Set newRanges = new HashSet<>(Arrays.asList( + new UidRange(vpnRange.start, APP1_UID - 1), + new UidRange(APP1_UID + 1, vpnRange.stop))); + mMockVpn.setUids(newRanges); + waitForIdle(); + + assertVpnUidRangesUpdated(true, newRanges, VPN_UID); + assertVpnUidRangesUpdated(false, vpnRanges, VPN_UID); + } + + @Test + public void testInvalidRequestTypes() { + final int[] invalidReqTypeInts = new int[]{-1, NetworkRequest.Type.NONE.ordinal(), + NetworkRequest.Type.LISTEN.ordinal(), NetworkRequest.Type.values().length}; + final NetworkCapabilities nc = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI); + + for (int reqTypeInt : invalidReqTypeInts) { + assertThrows("Expect throws for invalid request type " + reqTypeInt, + IllegalArgumentException.class, + () -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0, + null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE, + mContext.getPackageName(), getAttributionTag()) + ); + } + } + + private class QosCallbackMockHelper { + @NonNull public final QosFilter mFilter; + @NonNull public final IQosCallback mCallback; + @NonNull public final TestNetworkAgentWrapper mAgentWrapper; + @NonNull private final List mCallbacks = new ArrayList(); + + QosCallbackMockHelper() throws Exception { + Log.d(TAG, "QosCallbackMockHelper: "); + mFilter = mock(QosFilter.class); + + // Ensure the network is disconnected before anything else occurs + assertNull(mCellNetworkAgent); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + verifyActiveNetwork(TRANSPORT_CELLULAR); + waitForIdle(); + final Network network = mCellNetworkAgent.getNetwork(); + + final Pair pair = createQosCallback(); + mCallback = pair.first; + + when(mFilter.getNetwork()).thenReturn(network); + when(mFilter.validate()).thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mAgentWrapper = mCellNetworkAgent; + } + + void registerQosCallback(@NonNull final QosFilter filter, + @NonNull final IQosCallback callback) { + mCallbacks.add(callback); + final NetworkAgentInfo nai = + mService.getNetworkAgentInfoForNetwork(filter.getNetwork()); + mService.registerQosCallbackInternal(filter, callback, nai); + } + + void tearDown() { + for (int i = 0; i < mCallbacks.size(); i++) { + mService.unregisterQosCallback(mCallbacks.get(i)); + } + } + } + + private Pair createQosCallback() { + final IQosCallback callback = mock(IQosCallback.class); + final IBinder binder = mock(Binder.class); + when(callback.asBinder()).thenReturn(binder); + when(binder.isBinderAlive()).thenReturn(true); + return new Pair<>(callback, binder); + } + + + @Test + public void testQosCallbackRegistration() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + + final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 = + (NetworkAgentWrapper.CallbackType.OnQosCallbackRegister) + wrapper.getCallbackHistory().poll(1000, x -> true); + assertNotNull(cbRegister1); + + final int registerCallbackId = cbRegister1.mQosCallbackId; + mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback); + final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister; + cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister) + wrapper.getCallbackHistory().poll(1000, x -> true); + assertNotNull(cbUnregister); + assertEquals(registerCallbackId, cbUnregister.mQosCallbackId); + assertNull(wrapper.getCallbackHistory().poll(200, x -> true)); + } + + @Test + public void testQosCallbackNoRegistrationOnValidationError() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback) + .onError(eq(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED)); + } + + @Test + public void testQosCallbackAvailableAndLost() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final int sessionId = 10; + final int qosCallbackId = 1; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + + final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes( + 1, 2, 3, 4, 5, new ArrayList<>()); + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionAvailable(qosCallbackId, sessionId, attributes); + waitForIdle(); + + verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes)); + + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_EPS_BEARER)); + } + + @Test + public void testNrQosCallbackAvailableAndLost() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final int sessionId = 10; + final int qosCallbackId = 1; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + + final NrQosSessionAttributes attributes = new NrQosSessionAttributes( + 1, 2, 3, 4, 5, 6, 7, new ArrayList<>()); + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionAvailable(qosCallbackId, sessionId, attributes); + waitForIdle(); + + verify(mQosCallbackMockHelper.mCallback).onNrQosSessionAvailable(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_NR_BEARER), eq(attributes)); + + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_NR_BEARER); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_NR_BEARER)); + } + + @Test + public void testQosCallbackTooManyRequests() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + for (int i = 0; i < 100; i++) { + final Pair pair = createQosCallback(); + + try { + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, pair.first); + } catch (ServiceSpecificException e) { + assertEquals(e.errorCode, ConnectivityManager.Errors.TOO_MANY_REQUESTS); + if (i < 50) { + fail("TOO_MANY_REQUESTS thrown too early, the count is " + i); + } + + // As long as there is at least 50 requests, it is safe to assume it works. + // Note: The count isn't being tested precisely against 100 because the counter + // is shared with request network. + return; + } + } + fail("TOO_MANY_REQUESTS never thrown"); + } + + private UidRange createUidRange(int userId) { + return UidRange.createForUser(UserHandle.of(userId)); + } + + private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid) { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = uid; + try { + when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())) + .thenReturn(applicationInfo); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + private void mockGetApplicationInfoThrowsNameNotFound(@NonNull final String packageName) + throws Exception { + when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())) + .thenThrow(new PackageManager.NameNotFoundException(packageName)); + } + + private void mockHasSystemFeature(@NonNull final String featureName, + @NonNull final boolean hasFeature) { + when(mPackageManager.hasSystemFeature(eq(featureName))) + .thenReturn(hasFeature); + } + + private Range getNriFirstUidRange( + @NonNull final ConnectivityService.NetworkRequestInfo nri) { + return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next(); + } + + private OemNetworkPreferences createDefaultOemNetworkPreferences( + @OemNetworkPreferences.OemNetworkPreference final int preference) { + // Arrange PackageManager mocks + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + + // Build OemNetworkPreferences object + return new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, preference) + .build(); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError() + throws PackageManager.NameNotFoundException { + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OEM_NETWORK_PREFERENCE_UNINITIALIZED; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + assertThrows(IllegalArgumentException.class, + () -> mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest))); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceOemPaid() + throws Exception { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfRequests = 3; + + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OEM_NETWORK_PREFERENCE_OEM_PAID; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet nris = + mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest)); + + final List mRequests = nris.iterator().next().mRequests; + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfRequests, mRequests.size()); + assertTrue(mRequests.get(0).isListen()); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED)); + assertTrue(mRequests.get(1).isRequest()); + assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID)); + assertEquals(NetworkRequest.Type.TRACK_DEFAULT, mRequests.get(2).type); + assertTrue(mService.getDefaultRequest().networkCapabilities.equalsNetCapabilities( + mRequests.get(2).networkCapabilities)); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceOemPaidNoFallback() + throws Exception { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfRequests = 2; + + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet nris = + mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest)); + + final List mRequests = nris.iterator().next().mRequests; + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfRequests, mRequests.size()); + assertTrue(mRequests.get(0).isListen()); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED)); + assertTrue(mRequests.get(1).isRequest()); + assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID)); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceOemPaidOnly() + throws Exception { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfRequests = 1; + + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet nris = + mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest)); + + final List mRequests = nris.iterator().next().mRequests; + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfRequests, mRequests.size()); + assertTrue(mRequests.get(0).isRequest()); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID)); + } + + @Test + public void testOemNetworkRequestFactoryPreferenceOemPrivateOnly() + throws Exception { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfRequests = 1; + + @OemNetworkPreferences.OemNetworkPreference final int prefToTest = + OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet nris = + mService.new OemNetworkRequestFactory() + .createNrisFromOemNetworkPreferences( + createDefaultOemNetworkPreferences(prefToTest)); + + final List mRequests = nris.iterator().next().mRequests; + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfRequests, mRequests.size()); + assertTrue(mRequests.get(0).isRequest()); + assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PRIVATE)); + assertFalse(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID)); + } + + @Test + public void testOemNetworkRequestFactoryCreatesCorrectNumOfNris() + throws Exception { + // Expectations + final int expectedNumOfNris = 2; + + // Arrange PackageManager mocks + final String testPackageName2 = "com.google.apps.dialer"; + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + mockGetApplicationInfo(testPackageName2, TEST_PACKAGE_UID); + + // Build OemNetworkPreferences object + final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; + final int testOemPref2 = OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) + .addNetworkPreference(testPackageName2, testOemPref2) + .build(); + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet nris = + mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref); + + assertNotNull(nris); + assertEquals(expectedNumOfNris, nris.size()); + } + + @Test + public void testOemNetworkRequestFactoryMultiplePrefsCorrectlySetsUids() + throws Exception { + // Arrange PackageManager mocks + final String testPackageName2 = "com.google.apps.dialer"; + final int testPackageNameUid2 = 456; + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + mockGetApplicationInfo(testPackageName2, testPackageNameUid2); + + // Build OemNetworkPreferences object + final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; + final int testOemPref2 = OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) + .addNetworkPreference(testPackageName2, testOemPref2) + .build(); + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final List nris = + new ArrayList<>( + mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences( + pref)); + + // Sort by uid to access nris by index + nris.sort(Comparator.comparingInt(nri -> getNriFirstUidRange(nri).getLower())); + assertEquals(TEST_PACKAGE_UID, (int) getNriFirstUidRange(nris.get(0)).getLower()); + assertEquals(TEST_PACKAGE_UID, (int) getNriFirstUidRange(nris.get(0)).getUpper()); + assertEquals(testPackageNameUid2, (int) getNriFirstUidRange(nris.get(1)).getLower()); + assertEquals(testPackageNameUid2, (int) getNriFirstUidRange(nris.get(1)).getUpper()); + } + + @Test + public void testOemNetworkRequestFactoryMultipleUsersCorrectlySetsUids() + throws Exception { + // Arrange users + final int secondUser = 10; + final UserHandle secondUserHandle = new UserHandle(secondUser); + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle)); + + // Arrange PackageManager mocks + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + + // Build OemNetworkPreferences object + final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) + .build(); + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final List nris = + new ArrayList<>( + mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences( + pref)); + + // UIDs for all users and all managed packages should be present. + // Two users each with two packages. + final int expectedUidSize = 2; + final List> uids = + new ArrayList<>(nris.get(0).mRequests.get(0).networkCapabilities.getUids()); + assertEquals(expectedUidSize, uids.size()); + + // Sort by uid to access nris by index + uids.sort(Comparator.comparingInt(uid -> uid.getLower())); + final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID); + assertEquals(TEST_PACKAGE_UID, (int) uids.get(0).getLower()); + assertEquals(TEST_PACKAGE_UID, (int) uids.get(0).getUpper()); + assertEquals(secondUserTestPackageUid, (int) uids.get(1).getLower()); + assertEquals(secondUserTestPackageUid, (int) uids.get(1).getUpper()); + } + + @Test + public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference() + throws Exception { + // Expectations + final int expectedNumOfNris = 1; + final int expectedNumOfAppUids = 2; + + // Arrange PackageManager mocks + final String testPackageName2 = "com.google.apps.dialer"; + final int testPackageNameUid2 = 456; + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + mockGetApplicationInfo(testPackageName2, testPackageNameUid2); + + // Build OemNetworkPreferences object + final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) + .addNetworkPreference(testPackageName2, testOemPref) + .build(); + + // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() + final ArraySet nris = + mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref); + + assertEquals(expectedNumOfNris, nris.size()); + assertEquals(expectedNumOfAppUids, + nris.iterator().next().mRequests.get(0).networkCapabilities.getUids().size()); + } + + @Test + public void testSetOemNetworkPreferenceNullListenerAndPrefParamThrowsNpe() { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); + + // Act on ConnectivityService.setOemNetworkPreference() + assertThrows(NullPointerException.class, + () -> mService.setOemNetworkPreference( + null, + null)); + } + + @Test + public void testSetOemNetworkPreferenceFailsForNonAutomotive() + throws Exception { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + + // Act on ConnectivityService.setOemNetworkPreference() + assertThrows(UnsupportedOperationException.class, + () -> mService.setOemNetworkPreference( + createDefaultOemNetworkPreferences(networkPref), + new TestOemListenerCallback())); + } + + private void setOemNetworkPreferenceAgentConnected(final int transportType, + final boolean connectAgent) throws Exception { + switch(transportType) { + // Corresponds to a metered cellular network. Will be used for the default network. + case TRANSPORT_CELLULAR: + if (!connectAgent) { + mCellNetworkAgent.disconnect(); + break; + } + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + mCellNetworkAgent.connect(true); + break; + // Corresponds to a restricted ethernet network with OEM_PAID/OEM_PRIVATE. + case TRANSPORT_ETHERNET: + if (!connectAgent) { + stopOemManagedNetwork(); + break; + } + startOemManagedNetwork(true); + break; + // Corresponds to unmetered Wi-Fi. + case TRANSPORT_WIFI: + if (!connectAgent) { + mWiFiNetworkAgent.disconnect(); + break; + } + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + break; + default: + throw new AssertionError("Unsupported transport type passed in."); + + } + waitForIdle(); + } + + private void startOemManagedNetwork(final boolean isOemPaid) throws Exception { + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + mEthernetNetworkAgent.addCapability( + isOemPaid ? NET_CAPABILITY_OEM_PAID : NET_CAPABILITY_OEM_PRIVATE); + mEthernetNetworkAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + mEthernetNetworkAgent.connect(true); + } + + private void stopOemManagedNetwork() { + mEthernetNetworkAgent.disconnect(); + waitForIdle(); + } + + private void verifyMultipleDefaultNetworksTracksCorrectly( + final int expectedOemRequestsSize, + @NonNull final Network expectedDefaultNetwork, + @NonNull final Network expectedPerAppNetwork) { + // The current test setup assumes two tracked default network requests; one for the default + // network and the other for the OEM network preference being tested. This will be validated + // each time to confirm it doesn't change under test. + final int expectedDefaultNetworkRequestsSize = 2; + assertEquals(expectedDefaultNetworkRequestsSize, mService.mDefaultNetworkRequests.size()); + for (final ConnectivityService.NetworkRequestInfo defaultRequest + : mService.mDefaultNetworkRequests) { + final Network defaultNetwork = defaultRequest.getSatisfier() == null + ? null : defaultRequest.getSatisfier().network(); + // If this is the default request. + if (defaultRequest == mService.mDefaultRequest) { + assertEquals( + expectedDefaultNetwork, + defaultNetwork); + // Make sure this value doesn't change. + assertEquals(1, defaultRequest.mRequests.size()); + continue; + } + assertEquals(expectedPerAppNetwork, defaultNetwork); + assertEquals(expectedOemRequestsSize, defaultRequest.mRequests.size()); + } + verifyMultipleDefaultCallbacks(expectedDefaultNetwork, expectedPerAppNetwork); + } + + /** + * Verify default callbacks for 'available' fire as expected. This will only run if + * registerDefaultNetworkCallbacks() was executed prior and will only be different if the + * setOemNetworkPreference() per-app API was used for the current process. + * @param expectedSystemDefault the expected network for the system default. + * @param expectedPerAppDefault the expected network for the current process's default. + */ + private void verifyMultipleDefaultCallbacks( + @NonNull final Network expectedSystemDefault, + @NonNull final Network expectedPerAppDefault) { + if (null != mSystemDefaultNetworkCallback && null != expectedSystemDefault + && mService.mNoServiceNetwork.network() != expectedSystemDefault) { + // getLastAvailableNetwork() is used as this method can be called successively with + // the same network to validate therefore expectAvailableThenValidatedCallbacks + // can't be used. + assertEquals(mSystemDefaultNetworkCallback.getLastAvailableNetwork(), + expectedSystemDefault); + } + if (null != mDefaultNetworkCallback && null != expectedPerAppDefault + && mService.mNoServiceNetwork.network() != expectedPerAppDefault) { + assertEquals(mDefaultNetworkCallback.getLastAvailableNetwork(), + expectedPerAppDefault); + } + } + + private void registerDefaultNetworkCallbacks() { + // Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback() + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); + mSystemDefaultNetworkCallback = new TestNetworkCallback(); + mDefaultNetworkCallback = new TestNetworkCallback(); + mProfileDefaultNetworkCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); + registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback, + TEST_WORK_PROFILE_APP_UID); + // TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well. + mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); + } + + private void unregisterDefaultNetworkCallbacks() { + if (null != mDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mDefaultNetworkCallback); + } + if (null != mSystemDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback); + } + if (null != mProfileDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallback); + } + } + + private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( + @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup) + throws Exception { + final int testPackageNameUid = TEST_PACKAGE_UID; + final String testPackageName = "per.app.defaults.package"; + setupMultipleDefaultNetworksForOemNetworkPreferenceTest( + networkPrefToSetup, testPackageNameUid, testPackageName); + } + + private void setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest( + @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup) + throws Exception { + final int testPackageNameUid = Process.myUid(); + final String testPackageName = "per.app.defaults.package"; + setupMultipleDefaultNetworksForOemNetworkPreferenceTest( + networkPrefToSetup, testPackageNameUid, testPackageName); + } + + private void setupMultipleDefaultNetworksForOemNetworkPreferenceTest( + @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, + final int testPackageUid, @NonNull final String testPackageName) throws Exception { + // Only the default request should be included at start. + assertEquals(1, mService.mDefaultNetworkRequests.size()); + + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(testPackageUid)); + setupSetOemNetworkPreferenceForPreferenceTest( + networkPrefToSetup, uidRanges, testPackageName); + } + + private void setupSetOemNetworkPreferenceForPreferenceTest( + @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, + @NonNull final UidRangeParcel[] uidRanges, + @NonNull final String testPackageName) + throws Exception { + // These tests work off a single UID therefore using 'start' is valid. + mockGetApplicationInfo(testPackageName, uidRanges[0].start); + + setOemNetworkPreference(networkPrefToSetup, testPackageName); + } + + private void setOemNetworkPreference(final int networkPrefToSetup, + @NonNull final String... testPackageNames) + throws Exception { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); + + // Build OemNetworkPreferences object + final OemNetworkPreferences.Builder builder = new OemNetworkPreferences.Builder(); + for (final String packageName : testPackageNames) { + builder.addNetworkPreference(packageName, networkPrefToSetup); + } + final OemNetworkPreferences pref = builder.build(); + + // Act on ConnectivityService.setOemNetworkPreference() + final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback(); + mService.setOemNetworkPreference(pref, oemPrefListener); + + // Verify call returned successfully + oemPrefListener.expectOnComplete(); + } + + private static class TestOemListenerCallback implements IOnCompleteListener { + final CompletableFuture mDone = new CompletableFuture<>(); + + @Override + public void onComplete() { + mDone.complete(new Object()); + } + + void expectOnComplete() { + try { + mDone.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + fail("Expected onComplete() not received after " + TIMEOUT_MS + " ms"); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Override + public IBinder asBinder() { + return null; + } + } + + @Test + public void testMultiDefaultGetActiveNetworkIsCorrect() throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // Setup the test process to use networkPref for their default network. + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + // The active network for the default should be null at this point as this is a retricted + // network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Verify that the active network is correct + verifyActiveNetwork(TRANSPORT_ETHERNET); + // default NCs will be unregistered in tearDown + } + + @Test + public void testMultiDefaultIsActiveNetworkMeteredIsCorrect() throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // Setup the test process to use networkPref for their default network. + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + + // Returns true by default when no network is available. + assertTrue(mCm.isActiveNetworkMetered()); + + // Connect to an unmetered restricted network that will only be available to the OEM pref. + mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); + mEthernetNetworkAgent.addCapability(NET_CAPABILITY_OEM_PAID); + mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mEthernetNetworkAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + mEthernetNetworkAgent.connect(true); + waitForIdle(); + + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + assertFalse(mCm.isActiveNetworkMetered()); + // default NCs will be unregistered in tearDown + } + + @Test + public void testPerAppDefaultRegisterDefaultNetworkCallback() throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + final int expectedOemPrefRequestSize = 1; + final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); + + // Register the default network callback before the pref is already set. This means that + // the policy will be applied to the callback on setOemNetworkPreference(). + mCm.registerDefaultNetworkCallback(defaultNetworkCallback); + defaultNetworkCallback.assertNoCallback(); + + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackForUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + + // Setup the test process to use networkPref for their default network. + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + // The active nai for the default is null at this point as this is a restricted network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // At this point with a restricted network used, the available callback should trigger. + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); + assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), + mEthernetNetworkAgent.getNetwork()); + otherUidDefaultCallback.assertNoCallback(); + + // Now bring down the default network which should trigger a LOST callback. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + + // At this point, with no network is available, the lost callback should trigger + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); + + // Confirm we can unregister without issues. + mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); + } + + @Test + public void testPerAppDefaultRegisterDefaultNetworkCallbackAfterPrefSet() throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + final int expectedOemPrefRequestSize = 1; + final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); + + // Setup the test process to use networkPref for their default network. + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + + // Register the default network callback after the pref is already set. This means that + // the policy will be applied to the callback on requestNetwork(). + mCm.registerDefaultNetworkCallback(defaultNetworkCallback); + defaultNetworkCallback.assertNoCallback(); + + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackForUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + // The active nai for the default is null at this point as this is a restricted network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // At this point with a restricted network used, the available callback should trigger + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); + assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), + mEthernetNetworkAgent.getNetwork()); + otherUidDefaultCallback.assertNoCallback(); + + // Now bring down the default network which should trigger a LOST callback. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + otherUidDefaultCallback.assertNoCallback(); + + // At this point, with no network is available, the lost callback should trigger + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); + + // Confirm we can unregister without issues. + mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); + } + + @Test + public void testPerAppDefaultRegisterDefaultNetworkCallbackDoesNotFire() throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + final int expectedOemPrefRequestSize = 1; + final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); + final int userId = UserHandle.getUserId(Process.myUid()); + + mCm.registerDefaultNetworkCallback(defaultNetworkCallback); + defaultNetworkCallback.assertNoCallback(); + + final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); + withPermission(NETWORK_SETTINGS, () -> + mCm.registerDefaultNetworkCallbackForUid(TEST_PACKAGE_UID, otherUidDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper()))); + + // Setup a process different than the test process to use the default network. This means + // that the defaultNetworkCallback won't be tracked by the per-app policy. + setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + // The active nai for the default is null at this point as this is a restricted network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // As this callback does not have access to the OEM_PAID network, it will not fire. + defaultNetworkCallback.assertNoCallback(); + assertDefaultNetworkCapabilities(userId /* no networks */); + + // The other UID does have access, and gets a callback. + otherUidDefaultCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); + + // Bring up unrestricted cellular. This should now satisfy the default network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // At this point with an unrestricted network used, the available callback should trigger + // The other UID is unaffected and remains on the paid network. + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), + mCellNetworkAgent.getNetwork()); + assertDefaultNetworkCapabilities(userId, mCellNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); + + // Now bring down the per-app network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + + // Since the callback didn't use the per-app network, only the other UID gets a callback. + // Because the preference specifies no fallback, it does not switch to cellular. + defaultNetworkCallback.assertNoCallback(); + otherUidDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); + + // Now bring down the default network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + + // As this callback was tracking the default, this should now trigger. + defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + otherUidDefaultCallback.assertNoCallback(); + + // Confirm we can unregister without issues. + mCm.unregisterNetworkCallback(defaultNetworkCallback); + mCm.unregisterNetworkCallback(otherUidDefaultCallback); + } + + /** + * This method assumes that the same uidRanges input will be used to verify that dependencies + * are called as expected. + */ + private void verifySetOemNetworkPreferenceForPreference( + @NonNull final UidRangeParcel[] uidRanges, + final int addUidRangesNetId, + final int addUidRangesTimes, + final int removeUidRangesNetId, + final int removeUidRangesTimes, + final boolean shouldDestroyNetwork) throws RemoteException { + verifySetOemNetworkPreferenceForPreference(uidRanges, uidRanges, + addUidRangesNetId, addUidRangesTimes, removeUidRangesNetId, removeUidRangesTimes, + shouldDestroyNetwork); + } + + private void verifySetOemNetworkPreferenceForPreference( + @NonNull final UidRangeParcel[] addedUidRanges, + @NonNull final UidRangeParcel[] removedUidRanges, + final int addUidRangesNetId, + final int addUidRangesTimes, + final int removeUidRangesNetId, + final int removeUidRangesTimes, + final boolean shouldDestroyNetwork) throws RemoteException { + final boolean useAnyIdForAdd = OEM_PREF_ANY_NET_ID == addUidRangesNetId; + final boolean useAnyIdForRemove = OEM_PREF_ANY_NET_ID == removeUidRangesNetId; + + // Validate netd. + verify(mMockNetd, times(addUidRangesTimes)) + .networkAddUidRanges( + (useAnyIdForAdd ? anyInt() : eq(addUidRangesNetId)), eq(addedUidRanges)); + verify(mMockNetd, times(removeUidRangesTimes)) + .networkRemoveUidRanges( + (useAnyIdForRemove ? anyInt() : eq(removeUidRangesNetId)), + eq(removedUidRanges)); + if (shouldDestroyNetwork) { + verify(mMockNetd, times(1)) + .networkDestroy((useAnyIdForRemove ? anyInt() : eq(removeUidRangesNetId))); + } + reset(mMockNetd); + } + + /** + * Test the tracked default requests clear previous OEM requests on setOemNetworkPreference(). + */ + @Test + public void testSetOemNetworkPreferenceClearPreviousOemValues() throws Exception { + @OemNetworkPreferences.OemNetworkPreference int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID; + final int testPackageUid = 123; + final String testPackageName = "com.google.apps.contacts"; + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(testPackageUid)); + + // Validate the starting requests only includes the fallback request. + assertEquals(1, mService.mDefaultNetworkRequests.size()); + + // Add an OEM default network request to track. + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, testPackageName); + + // Two requests should exist, one for the fallback and one for the pref. + assertEquals(2, mService.mDefaultNetworkRequests.size()); + + networkPref = OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, testPackageName); + + // Two requests should still exist validating the previous per-app request was replaced. + assertEquals(2, mService.mDefaultNetworkRequests.size()); + } + + /** + * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback + */ + @Test + public void testMultilayerForPreferenceOemPaidEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID; + + // Arrange PackageManager mocks + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); + + // Verify the starting state. No networks should be connected. + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mWiFiNetworkAgent.getNetwork().netId, 1 /* times */, + mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + + // Disconnecting OEM_PAID should have no effect as it is lower in priority then unmetered. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + // netd should not be called as default networks haven't changed. + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Disconnecting unmetered should put PANS on lowest priority fallback request. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + mWiFiNetworkAgent.getNetwork().netId, 0 /* times */, + true /* shouldDestroyNetwork */); + + // Disconnecting the fallback network should result in no connectivity. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + mCellNetworkAgent.getNetwork().netId, 0 /* times */, + true /* shouldDestroyNetwork */); + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID + */ + @Test + public void testMultilayerForPreferenceOemPaidNoFallbackEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + + // Arrange PackageManager mocks + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); + + // Verify the starting state. This preference doesn't support using the fallback network + // therefore should be on the disconnected network as it has no networks to connect to. + verifySetOemNetworkPreferenceForPreference(uidRanges, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + // This preference should not use this network as it doesn't support fallback usage. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mWiFiNetworkAgent.getNetwork().netId, 1 /* times */, + mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + + // Disconnecting unmetered should put PANS on OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, + mWiFiNetworkAgent.getNetwork().netId, 0 /* times */, + true /* shouldDestroyNetwork */); + + // Disconnecting OEM_PAID should result in no connectivity. + // OEM_PAID_NO_FALLBACK not supporting a fallback now uses the disconnected network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + mEthernetNetworkAgent.getNetwork().netId, 0 /* times */, + true /* shouldDestroyNetwork */); + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: + * NET_CAPABILITY_OEM_PAID + * This preference should only apply to OEM_PAID networks. + */ + @Test + public void testMultilayerForPreferenceOemPaidOnlyEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + + // Arrange PackageManager mocks + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); + + // Verify the starting state. This preference doesn't support using the fallback network + // therefore should be on the disconnected network as it has no networks to connect to. + verifySetOemNetworkPreferenceForPreference(uidRanges, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up metered cellular. This should not apply to this preference. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up unmetered Wi-Fi. This should not apply to this preference. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + false /* shouldDestroyNetwork */); + + // Disconnecting OEM_PAID should result in no connectivity. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + mEthernetNetworkAgent.getNetwork().netId, 0 /* times */, + true /* shouldDestroyNetwork */); + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: + * NET_CAPABILITY_OEM_PRIVATE + * This preference should only apply to OEM_PRIVATE networks. + */ + @Test + public void testMultilayerForPreferenceOemPrivateOnlyEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + + // Arrange PackageManager mocks + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); + + // Verify the starting state. This preference doesn't support using the fallback network + // therefore should be on the disconnected network as it has no networks to connect to. + verifySetOemNetworkPreferenceForPreference(uidRanges, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up metered cellular. This should not apply to this preference. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up unmetered Wi-Fi. This should not apply to this preference. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE. + startOemManagedNetwork(false); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + false /* shouldDestroyNetwork */); + + // Disconnecting OEM_PRIVATE should result in no connectivity. + stopOemManagedNetwork(); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, + mEthernetNetworkAgent.getNetwork().netId, 0 /* times */, + true /* shouldDestroyNetwork */); + } + + @Test + public void testMultilayerForMultipleUsersEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID; + + // Arrange users + final int secondUser = 10; + final UserHandle secondUserHandle = new UserHandle(secondUser); + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle)); + + // Arrange PackageManager mocks + final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID); + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels( + uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid)); + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); + + // Verify the starting state. No networks should be connected. + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test that we correctly add the expected values for multiple users. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRanges, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test that we correctly remove the expected values for multiple users. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifySetOemNetworkPreferenceForPreference(uidRanges, + OEM_PREF_ANY_NET_ID, 0 /* times */, + mCellNetworkAgent.getNetwork().netId, 0 /* times */, + true /* shouldDestroyNetwork */); + } + + @Test + public void testMultilayerForBroadcastedUsersEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID; + + // Arrange users + final int secondUser = 10; + final UserHandle secondUserHandle = new UserHandle(secondUser); + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE)); + + // Arrange PackageManager mocks + final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID); + final UidRangeParcel[] uidRangesSingleUser = + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); + final UidRangeParcel[] uidRangesBothUsers = + toUidRangeStableParcels( + uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid)); + setupSetOemNetworkPreferenceForPreferenceTest( + networkPref, uidRangesSingleUser, TEST_PACKAGE_NAME); + + // Verify the starting state. No networks should be connected. + verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test that we correctly add the expected values for multiple users. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Send a broadcast indicating a user was added. + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle)); + final Intent addedIntent = new Intent(ACTION_USER_ADDED); + addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(secondUser)); + processBroadcast(addedIntent); + + // Test that we correctly add values for all users and remove for the single user. + verifySetOemNetworkPreferenceForPreference(uidRangesBothUsers, uidRangesSingleUser, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + + // Send a broadcast indicating a user was removed. + when(mUserManager.getUserHandles(anyBoolean())).thenReturn( + Arrays.asList(PRIMARY_USER_HANDLE)); + final Intent removedIntent = new Intent(ACTION_USER_REMOVED); + removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(secondUser)); + processBroadcast(removedIntent); + + // Test that we correctly add values for the single user and remove for the all users. + verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, uidRangesBothUsers, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + } + + @Test + public void testMultilayerForPackageChangesEvaluatesCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID; + final String packageScheme = "package:"; + + // Arrange PackageManager mocks + final String packageToInstall = "package.to.install"; + final int packageToInstallUid = 81387; + final UidRangeParcel[] uidRangesSinglePackage = + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); + mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); + mockGetApplicationInfoThrowsNameNotFound(packageToInstall); + setOemNetworkPreference(networkPref, TEST_PACKAGE_NAME, packageToInstall); + grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid(), packageToInstall); + + // Verify the starting state. No networks should be connected. + verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage, + OEM_PREF_ANY_NET_ID, 0 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Test that we correctly add the expected values for installed packages. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + OEM_PREF_ANY_NET_ID, 0 /* times */, + false /* shouldDestroyNetwork */); + + // Set the system to recognize the package to be installed + mockGetApplicationInfo(packageToInstall, packageToInstallUid); + final UidRangeParcel[] uidRangesAllPackages = + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID, packageToInstallUid)); + + // Send a broadcast indicating a package was installed. + final Intent addedIntent = new Intent(ACTION_PACKAGE_ADDED); + addedIntent.setData(Uri.parse(packageScheme + packageToInstall)); + processBroadcast(addedIntent); + + // Test the single package is removed and the combined packages are added. + verifySetOemNetworkPreferenceForPreference(uidRangesAllPackages, uidRangesSinglePackage, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + + // Set the system to no longer recognize the package to be installed + mockGetApplicationInfoThrowsNameNotFound(packageToInstall); + + // Send a broadcast indicating a package was removed. + final Intent removedIntent = new Intent(ACTION_PACKAGE_REMOVED); + removedIntent.setData(Uri.parse(packageScheme + packageToInstall)); + processBroadcast(removedIntent); + + // Test the combined packages are removed and the single package is added. + verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage, uidRangesAllPackages, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + + // Set the system to change the installed package's uid + final int replacedTestPackageUid = TEST_PACKAGE_UID + 1; + mockGetApplicationInfo(TEST_PACKAGE_NAME, replacedTestPackageUid); + final UidRangeParcel[] uidRangesReplacedPackage = + toUidRangeStableParcels(uidRangesForUids(replacedTestPackageUid)); + + // Send a broadcast indicating a package was replaced. + final Intent replacedIntent = new Intent(ACTION_PACKAGE_REPLACED); + replacedIntent.setData(Uri.parse(packageScheme + TEST_PACKAGE_NAME)); + processBroadcast(replacedIntent); + + // Test the original uid is removed and is replaced with the new uid. + verifySetOemNetworkPreferenceForPreference(uidRangesReplacedPackage, uidRangesSinglePackage, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + mCellNetworkAgent.getNetwork().netId, 1 /* times */, + false /* shouldDestroyNetwork */); + } + + /** + * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 3; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mCellNetworkAgent.getNetwork()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mWiFiNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi will put the pref on OEM_PAID and fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID will put both on null as it is the last network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + null); + + // default callbacks will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidNoFallbackCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 2; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network but not the pref. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mWiFiNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi will put the OEM pref on OEM_PAID and fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID puts the fallback on null and the pref on the disconnected net. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default callbacks will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: + * NET_CAPABILITY_OEM_PAID + * This preference should only apply to OEM_PAID networks. + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidOnlyCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID will keep the fallback on cellular and nothing for OEM_PAID. + // OEM_PAID_ONLY not supporting a fallback now uses the disconnected network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Disconnecting cellular will put the fallback on null and the pref on disconnected. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default callbacks will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: + * NET_CAPABILITY_OEM_PRIVATE + * This preference should only apply to OEM_PRIVATE networks. + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPrivateOnlyCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE. + startOemManagedNetwork(false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PRIVATE will keep the fallback on cellular. + // OEM_PRIVATE_ONLY not supporting a fallback now uses to the disconnected network. + stopOemManagedNetwork(); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Disconnecting cellular will put the fallback on null and pref on disconnected. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default callbacks will be unregistered in tearDown + } + + @Test + public void testCapabilityWithOemNetworkPreference() throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref); + registerDefaultNetworkCallbacks(); + + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + + // default callbacks will be unregistered in tearDown + } + + @Test + public void testSetOemNetworkPreferenceLogsRequest() throws Exception { + mServiceContext.setPermission(DUMP, PERMISSION_GRANTED); + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PAID; + final StringWriter stringWriter = new StringWriter(); + final String logIdentifier = "UPDATE INITIATED: OemNetworkPreferences"; + final Pattern pattern = Pattern.compile(logIdentifier); + + final int expectedNumLogs = 2; + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); + + // Call twice to generate two logs. + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); + setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); + mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]); + + final String dumpOutput = stringWriter.toString(); + final Matcher matcher = pattern.matcher(dumpOutput); + int count = 0; + while (matcher.find()) { + count++; + } + assertEquals(expectedNumLogs, count); + } + + @Test + public void testGetAllNetworkStateSnapshots() throws Exception { + verifyNoNetwork(); + + // Setup test cellular network with specified LinkProperties and NetworkCapabilities, + // verify the content of the snapshot matches. + final LinkProperties cellLp = new LinkProperties(); + final LinkAddress myIpv4Addr = new LinkAddress(InetAddress.getByName("192.0.2.129"), 25); + final LinkAddress myIpv6Addr = new LinkAddress(InetAddress.getByName("2001:db8::1"), 64); + cellLp.setInterfaceName("test01"); + cellLp.addLinkAddress(myIpv4Addr); + cellLp.addLinkAddress(myIpv6Addr); + cellLp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + cellLp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + cellLp.addRoute(new RouteInfo(myIpv4Addr, null)); + cellLp.addRoute(new RouteInfo(myIpv6Addr, null)); + final NetworkCapabilities cellNcTemplate = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_MMS).build(); + + final TestNetworkCallback cellCb = new TestNetworkCallback(); + mCm.requestNetwork(new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(), + cellCb); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp, cellNcTemplate); + mCellNetworkAgent.connect(true); + cellCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + List snapshots = mCm.getAllNetworkStateSnapshots(); + assertLength(1, snapshots); + + // Compose the expected cellular snapshot for verification. + final NetworkCapabilities cellNc = + mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()); + final NetworkStateSnapshot cellSnapshot = new NetworkStateSnapshot( + mCellNetworkAgent.getNetwork(), cellNc, cellLp, + null, ConnectivityManager.TYPE_MOBILE); + assertEquals(cellSnapshot, snapshots.get(0)); + + // Connect wifi and verify the snapshots. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + // Compose the expected wifi snapshot for verification. + final NetworkCapabilities wifiNc = + mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()); + final NetworkStateSnapshot wifiSnapshot = new NetworkStateSnapshot( + mWiFiNetworkAgent.getNetwork(), wifiNc, new LinkProperties(), null, + ConnectivityManager.TYPE_WIFI); + + snapshots = mCm.getAllNetworkStateSnapshots(); + assertLength(2, snapshots); + assertContainsAll(snapshots, cellSnapshot, wifiSnapshot); + + // Set cellular as suspended, verify the snapshots will not contain suspended networks. + // TODO: Consider include SUSPENDED networks, which should be considered as + // temporary shortage of connectivity of a connected network. + mCellNetworkAgent.suspend(); + waitForIdle(); + snapshots = mCm.getAllNetworkStateSnapshots(); + assertLength(1, snapshots); + assertEquals(wifiSnapshot, snapshots.get(0)); + + // Disconnect wifi, verify the snapshots contain nothing. + mWiFiNetworkAgent.disconnect(); + waitForIdle(); + snapshots = mCm.getAllNetworkStateSnapshots(); + assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); + assertLength(0, snapshots); + + mCellNetworkAgent.resume(); + waitForIdle(); + snapshots = mCm.getAllNetworkStateSnapshots(); + assertLength(1, snapshots); + assertEquals(cellSnapshot, snapshots.get(0)); + + mCellNetworkAgent.disconnect(); + waitForIdle(); + verifyNoNetwork(); + mCm.unregisterNetworkCallback(cellCb); + } + + // Cannot be part of MockNetworkFactory since it requires method of the test. + private void expectNoRequestChanged(@NonNull MockNetworkFactory factory) { + waitForIdle(); + factory.assertNoRequestChanged(); + } + + @Test + public void testRegisterBestMatchingNetworkCallback_noIssueToFactory() throws Exception { + // Prepare mock mms factory. + final HandlerThread handlerThread = new HandlerThread("MockCellularFactory"); + handlerThread.start(); + NetworkCapabilities filter = new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_MMS); + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter, mCsHandlerThread); + testFactory.setScoreFilter(40); + + try { + // Register the factory and expect it will see default request, because all requests + // are sent to all factories. + testFactory.register(); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(1); + // The factory won't try to start the network since the default request doesn't + // match the filter (no INTERNET capability). + assertFalse(testFactory.getMyStartRequested()); + + // Register callback for listening best matching network. Verify that the request won't + // be sent to factory. + final TestNetworkCallback bestMatchingCb = new TestNetworkCallback(); + mCm.registerBestMatchingNetworkCallback( + new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(), + bestMatchingCb, mCsHandlerThread.getThreadHandler()); + bestMatchingCb.assertNoCallback(); + expectNoRequestChanged(testFactory); + testFactory.assertRequestCountEquals(1); + assertFalse(testFactory.getMyStartRequested()); + + // Fire a normal mms request, verify the factory will only see the request. + final TestNetworkCallback mmsNetworkCallback = new TestNetworkCallback(); + final NetworkRequest mmsRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_MMS).build(); + mCm.requestNetwork(mmsRequest, mmsNetworkCallback); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(2); + assertTrue(testFactory.getMyStartRequested()); + + // Unregister best matching callback, verify factory see no change. + mCm.unregisterNetworkCallback(bestMatchingCb); + expectNoRequestChanged(testFactory); + testFactory.assertRequestCountEquals(2); + assertTrue(testFactory.getMyStartRequested()); + } finally { + testFactory.terminate(); + } + } + + @Test + public void testRegisterBestMatchingNetworkCallback_trackBestNetwork() throws Exception { + final TestNetworkCallback bestMatchingCb = new TestNetworkCallback(); + mCm.registerBestMatchingNetworkCallback( + new NetworkRequest.Builder().addCapability(NET_CAPABILITY_TRUSTED).build(), + bestMatchingCb, mCsHandlerThread.getThreadHandler()); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + bestMatchingCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + + // Change something on cellular to trigger capabilities changed, since the callback + // only cares about the best network, verify it received nothing from cellular. + mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + bestMatchingCb.assertNoCallback(); + + // Make cellular the best network again, verify the callback now tracks cellular. + mWiFiNetworkAgent.adjustScore(-50); + bestMatchingCb.expectAvailableCallbacksValidated(mCellNetworkAgent); + + // Make cellular temporary non-trusted, which will not satisfying the request. + // Verify the callback switch from/to the other network accordingly. + mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); + bestMatchingCb.expectAvailableCallbacksValidated(mWiFiNetworkAgent); + mCellNetworkAgent.addCapability(NET_CAPABILITY_TRUSTED); + bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mCellNetworkAgent); + + // Verify the callback doesn't care about wifi disconnect. + mWiFiNetworkAgent.disconnect(); + bestMatchingCb.assertNoCallback(); + mCellNetworkAgent.disconnect(); + bestMatchingCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + } + + private UidRangeParcel[] uidRangeFor(final UserHandle handle) { + UidRange range = UidRange.createForUser(handle); + return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) }; + } + + private static class TestOnCompleteListener implements Runnable { + final class OnComplete {} + final ArrayTrackRecord.ReadHead mHistory = + new ArrayTrackRecord().newReadHead(); + + @Override + public void run() { + mHistory.add(new OnComplete()); + } + + public void expectOnComplete() { + assertNotNull(mHistory.poll(TIMEOUT_MS, it -> true)); + } + } + + private TestNetworkAgentWrapper makeEnterpriseNetworkAgent() throws Exception { + final NetworkCapabilities workNc = new NetworkCapabilities(); + workNc.addCapability(NET_CAPABILITY_ENTERPRISE); + workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc); + } + + private TestNetworkCallback mEnterpriseCallback; + private UserHandle setupEnterpriseNetwork() { + final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(userHandle, true); + + // File a request to avoid the enterprise network being disconnected as soon as the default + // request goes away – it would make impossible to test that networkRemoveUidRanges + // is called, as the network would disconnect first for lack of a request. + mEnterpriseCallback = new TestNetworkCallback(); + final NetworkRequest keepUpRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_ENTERPRISE) + .build(); + mCm.requestNetwork(keepUpRequest, mEnterpriseCallback); + return userHandle; + } + + private void maybeTearDownEnterpriseNetwork() { + if (null != mEnterpriseCallback) { + mCm.unregisterNetworkCallback(mEnterpriseCallback); + } + } + + /** + * Make sure per-profile networking preference behaves as expected when the enterprise network + * goes up and down while the preference is active. Make sure they behave as expected whether + * there is a general default network or not. + */ + @Test + public void testPreferenceForUserNetworkUpDown() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + registerDefaultNetworkCallbacks(); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( + mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); + + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + + // Setting a network preference for this user will create a new set of routing rules for + // the UID range that corresponds to this user, so as to define the default network + // for these apps separately. This is true because the multi-layer request relevant to + // this UID range contains a TRACK_DEFAULT, so the range will be moved through UID-specific + // rules to the correct network – in this case the system default network. The case where + // the default network for the profile happens to be the same as the system default + // is not handled specially, the rules are always active as long as a preference is set. + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + // The enterprise network is not ready yet. + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(false); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent); + mSystemDefaultNetworkCallback.assertNoCallback(); + mDefaultNetworkCallback.assertNoCallback(); + inOrder.verify(mMockNetd).networkCreate( + nativeNetworkConfigPhysical(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM)); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + // Make sure changes to the work agent send callbacks to the app in the work profile, but + // not to the other apps. + workAgent.setNetworkValid(true /* isStrictMode */); + workAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, + nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED) + && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + + workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + + // Conversely, change a capability on the system-wide default network and make sure + // that only the apps outside of the work profile receive the callbacks. + mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); + mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> + nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); + mProfileDefaultNetworkCallback.assertNoCallback(); + + // Disconnect and reconnect the system-wide default network and make sure that the + // apps on this network see the appropriate callbacks, and the app on the work profile + // doesn't because it continues to use the enterprise network. + mCellNetworkAgent.disconnect(); + mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mProfileDefaultNetworkCallback.assertNoCallback(); + inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.assertNoCallback(); + inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( + mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); + + // When the agent disconnects, test that the app on the work profile falls back to the + // default network. + workAgent.disconnect(); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent); + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId); + + mCellNetworkAgent.disconnect(); + mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + + // Waiting for the handler to be idle before checking for networkDestroy is necessary + // here because ConnectivityService calls onLost before the network is fully torn down. + waitForIdle(); + inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); + + // If the control comes here, callbacks seem to behave correctly in the presence of + // a default network when the enterprise network goes up and down. Now, make sure they + // also behave correctly in the absence of a system-wide default network. + final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(); + workAgent2.connect(false); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( + workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM)); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent2.getNetwork().netId, + uidRangeFor(testHandle)); + + workAgent2.setNetworkValid(true /* isStrictMode */); + workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid()); + mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2, + nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE) + && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any()); + + // When the agent disconnects, test that the app on the work profile falls back to the + // default network. + workAgent2.disconnect(); + mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId); + + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + // Callbacks will be unregistered by tearDown() + } + + /** + * Test that, in a given networking context, calling setPreferenceForUser to set per-profile + * defaults on then off works as expected. + */ + @Test + public void testSetPreferenceForUserOnOff() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + + // Connect both a regular cell agent and an enterprise network first. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(true); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( + mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + registerDefaultNetworkCallbacks(); + + mSystemDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); + + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT, + r -> r.run(), listener); + listener.expectOnComplete(); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); + inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + workAgent.disconnect(); + mCellNetworkAgent.disconnect(); + + // Callbacks will be unregistered by tearDown() + } + + /** + * Test per-profile default networks for two different profiles concurrently. + */ + @Test + public void testSetPreferenceForTwoProfiles() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle2 = setupEnterpriseNetwork(); + final UserHandle testHandle4 = UserHandle.of(TEST_WORK_PROFILE_USER_ID + 2); + mServiceContext.setWorkProfile(testHandle4, true); + registerDefaultNetworkCallbacks(); + + final TestNetworkCallback app4Cb = new TestNetworkCallback(); + final int testWorkProfileAppUid4 = + UserHandle.getUid(testHandle4.getIdentifier(), TEST_APP_ID); + registerDefaultNetworkCallbackAsUid(app4Cb, testWorkProfileAppUid4); + + // Connect both a regular cell agent and an enterprise network first. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); + workAgent.connect(true); + + mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + app4Cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( + mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); + inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( + workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM)); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle2)); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + app4Cb); + + mCm.setProfileNetworkPreference(testHandle4, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkAddUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle4)); + + app4Cb.expectAvailableCallbacksValidated(workAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + mProfileDefaultNetworkCallback); + + mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_DEFAULT, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkRemoveUidRanges(workAgent.getNetwork().netId, + uidRangeFor(testHandle2)); + + mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, + app4Cb); + + workAgent.disconnect(); + mCellNetworkAgent.disconnect(); + + mCm.unregisterNetworkCallback(app4Cb); + // Other callbacks will be unregistered by tearDown() + } + + @Test + public void testProfilePreferenceRemovedUponUserRemoved() throws Exception { + final InOrder inOrder = inOrder(mMockNetd); + final UserHandle testHandle = setupEnterpriseNetwork(); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( + mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); + inOrder.verify(mMockNetd).networkAddUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + + final Intent removedIntent = new Intent(ACTION_USER_REMOVED); + removedIntent.putExtra(Intent.EXTRA_USER, testHandle); + processBroadcast(removedIntent); + + inOrder.verify(mMockNetd).networkRemoveUidRanges(mCellNetworkAgent.getNetwork().netId, + uidRangeFor(testHandle)); + } + + /** + * Make sure that OEM preference and per-profile preference can't be used at the same + * time and throw ISE if tried + */ + @Test + public void testOemPreferenceAndProfilePreferenceExclusive() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, true); + final TestOnCompleteListener listener = new TestOnCompleteListener(); + + setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( + OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY); + assertThrows("Should not be able to set per-profile pref while OEM prefs present", + IllegalStateException.class, () -> + mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener)); + + // Empty the OEM prefs + final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback(); + final OemNetworkPreferences emptyOemPref = new OemNetworkPreferences.Builder().build(); + mService.setOemNetworkPreference(emptyOemPref, oemPrefListener); + oemPrefListener.expectOnComplete(); + + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + r -> r.run(), listener); + listener.expectOnComplete(); + assertThrows("Should not be able to set OEM prefs while per-profile pref is on", + IllegalStateException.class , () -> + mService.setOemNetworkPreference(emptyOemPref, oemPrefListener)); + } + + /** + * Make sure wrong preferences for per-profile default networking are rejected. + */ + @Test + public void testProfileNetworkPrefWrongPreference() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, true); + assertThrows("Should not be able to set an illegal preference", + IllegalArgumentException.class, + () -> mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE + 1, null, null)); + } + + /** + * Make sure requests for per-profile default networking for a non-work profile are + * rejected + */ + @Test + public void testProfileNetworkPrefWrongProfile() throws Exception { + final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); + mServiceContext.setWorkProfile(testHandle, false); + assertThrows("Should not be able to set a user pref for a non-work profile", + IllegalArgumentException.class , () -> + mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null)); + } + + @Test + public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception { + mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED); + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setSubscriptionIds(Collections.singleton(Process.myUid())); + + final NetworkCapabilities result = + mService.networkCapabilitiesRestrictedForCallerPermissions( + nc, Process.myPid(), Process.myUid()); + assertTrue(result.getSubscriptionIds().isEmpty()); + } + + @Test + public void testSubIdsExistWithNetworkFactoryPermission() throws Exception { + mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED); + + final Set subIds = Collections.singleton(Process.myUid()); + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setSubscriptionIds(subIds); + + final NetworkCapabilities result = + mService.networkCapabilitiesRestrictedForCallerPermissions( + nc, Process.myPid(), Process.myUid()); + assertEquals(subIds, result.getSubscriptionIds()); + } + + private NetworkRequest getRequestWithSubIds() { + return new NetworkRequest.Builder() + .setSubscriptionIds(Collections.singleton(Process.myUid())) + .build(); + } + + @Test + public void testNetworkRequestWithSubIdsWithNetworkFactoryPermission() throws Exception { + mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED); + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE); + final NetworkCallback networkCallback1 = new NetworkCallback(); + final NetworkCallback networkCallback2 = new NetworkCallback(); + + mCm.requestNetwork(getRequestWithSubIds(), networkCallback1); + mCm.requestNetwork(getRequestWithSubIds(), pendingIntent); + mCm.registerNetworkCallback(getRequestWithSubIds(), networkCallback2); + + mCm.unregisterNetworkCallback(networkCallback1); + mCm.releaseNetworkRequest(pendingIntent); + mCm.unregisterNetworkCallback(networkCallback2); + } + + @Test + public void testNetworkRequestWithSubIdsWithoutNetworkFactoryPermission() throws Exception { + mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED); + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE); + + final Class expected = SecurityException.class; + assertThrows( + expected, () -> mCm.requestNetwork(getRequestWithSubIds(), new NetworkCallback())); + assertThrows(expected, () -> mCm.requestNetwork(getRequestWithSubIds(), pendingIntent)); + assertThrows( + expected, + () -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback())); + } + + /** + * Validate request counts are counted accurately on setProfileNetworkPreference on set/replace. + */ + @Test + public void testProfileNetworkPrefCountsRequestsCorrectlyOnSet() throws Exception { + final UserHandle testHandle = setupEnterpriseNetwork(); + testRequestCountLimits(() -> { + // Set initially to test the limit prior to having existing requests. + final TestOnCompleteListener listener = new TestOnCompleteListener(); + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + Runnable::run, listener); + listener.expectOnComplete(); + + // re-set so as to test the limit as part of replacing existing requests. + mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + Runnable::run, listener); + listener.expectOnComplete(); + }); + } + + /** + * Validate request counts are counted accurately on setOemNetworkPreference on set/replace. + */ + @Test + public void testSetOemNetworkPreferenceCountsRequestsCorrectlyOnSet() throws Exception { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + testRequestCountLimits(() -> { + // Set initially to test the limit prior to having existing requests. + final TestOemListenerCallback listener = new TestOemListenerCallback(); + mService.setOemNetworkPreference( + createDefaultOemNetworkPreferences(networkPref), listener); + listener.expectOnComplete(); + + // re-set so as to test the limit as part of replacing existing requests. + mService.setOemNetworkPreference( + createDefaultOemNetworkPreferences(networkPref), listener); + listener.expectOnComplete(); + }); + } + + private void testRequestCountLimits(@NonNull final Runnable r) throws Exception { + final ArraySet callbacks = new ArraySet<>(); + try { + final int requestCount = mService.mSystemNetworkRequestCounter + .mUidToNetworkRequestCount.get(Process.myUid()); + // The limit is hit when total requests <= limit. + final int maxCount = + ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - requestCount; + // Need permission so registerDefaultNetworkCallback uses mSystemNetworkRequestCounter + withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> { + for (int i = 1; i < maxCount - 1; i++) { + final TestNetworkCallback cb = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(cb); + callbacks.add(cb); + } + + // Code to run to check if it triggers a max request count limit error. + r.run(); + }); + } finally { + for (final TestNetworkCallback cb : callbacks) { + mCm.unregisterNetworkCallback(cb); + } + } + } +} diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java new file mode 100644 index 0000000000..cf2c9c783a --- /dev/null +++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java @@ -0,0 +1,1004 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.INetd.IF_STATE_DOWN; +import static android.net.INetd.IF_STATE_UP; +import static android.net.IpSecManager.DIRECTION_FWD; +import static android.net.IpSecManager.DIRECTION_IN; +import static android.net.IpSecManager.DIRECTION_OUT; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.INetd; +import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; +import android.net.IpSecAlgorithm; +import android.net.IpSecConfig; +import android.net.IpSecManager; +import android.net.IpSecSpiResponse; +import android.net.IpSecTransform; +import android.net.IpSecTransformResponse; +import android.net.IpSecTunnelInterfaceResponse; +import android.net.IpSecUdpEncapResponse; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.os.Binder; +import android.os.ParcelFileDescriptor; +import android.system.Os; +import android.test.mock.MockContext; +import android.util.ArraySet; + +import androidx.test.filters.SmallTest; + +import com.android.server.IpSecService.TunnelInterfaceRecord; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.net.Inet4Address; +import java.net.Socket; +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; + +/** Unit tests for {@link IpSecService}. */ +@SmallTest +@RunWith(Parameterized.class) +public class IpSecServiceParameterizedTest { + + private static final int TEST_SPI = 0xD1201D; + + private final String mSourceAddr; + private final String mDestinationAddr; + private final LinkAddress mLocalInnerAddress; + private final int mFamily; + + private static final int[] ADDRESS_FAMILIES = + new int[] {AF_INET, AF_INET6}; + + @Parameterized.Parameters + public static Collection ipSecConfigs() { + return Arrays.asList( + new Object[][] { + {"1.2.3.4", "8.8.4.4", "10.0.1.1/24", AF_INET}, + {"2601::2", "2601::10", "2001:db8::1/64", AF_INET6} + }); + } + + private static final byte[] AEAD_KEY = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x73, 0x61, 0x6C, 0x74 + }; + private static final byte[] CRYPT_KEY = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + private static final byte[] AUTH_KEY = { + 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, + 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F + }; + + AppOpsManager mMockAppOps = mock(AppOpsManager.class); + ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class); + + TestContext mTestContext = new TestContext(); + + private class TestContext extends MockContext { + private Set mAllowedPermissions = new ArraySet<>(Arrays.asList( + android.Manifest.permission.MANAGE_IPSEC_TUNNELS, + android.Manifest.permission.NETWORK_STACK, + PERMISSION_MAINLINE_NETWORK_STACK)); + + private void setAllowedPermissions(String... permissions) { + mAllowedPermissions = new ArraySet<>(permissions); + } + + @Override + public Object getSystemService(String name) { + switch(name) { + case Context.APP_OPS_SERVICE: + return mMockAppOps; + case Context.CONNECTIVITY_SERVICE: + return mMockConnectivityMgr; + default: + return null; + } + } + + @Override + public String getSystemServiceName(Class serviceClass) { + if (ConnectivityManager.class == serviceClass) { + return Context.CONNECTIVITY_SERVICE; + } + return null; + } + + @Override + public PackageManager getPackageManager() { + return mMockPkgMgr; + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + if (mAllowedPermissions.contains(permission)) { + return; + } else { + throw new SecurityException("Unavailable permission requested"); + } + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + if (mAllowedPermissions.contains(permission)) { + return PERMISSION_GRANTED; + } else { + return PERMISSION_DENIED; + } + } + } + + INetd mMockNetd; + PackageManager mMockPkgMgr; + IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; + IpSecService mIpSecService; + Network fakeNetwork = new Network(0xAB); + int mUid = Os.getuid(); + + private static final IpSecAlgorithm AUTH_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4); + private static final IpSecAlgorithm CRYPT_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY); + private static final IpSecAlgorithm AEAD_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); + private static final int REMOTE_ENCAP_PORT = 4500; + + private static final String BLESSED_PACKAGE = "blessedPackage"; + private static final String SYSTEM_PACKAGE = "systemPackage"; + private static final String BAD_PACKAGE = "badPackage"; + + public IpSecServiceParameterizedTest( + String sourceAddr, String destAddr, String localInnerAddr, int family) { + mSourceAddr = sourceAddr; + mDestinationAddr = destAddr; + mLocalInnerAddress = new LinkAddress(localInnerAddr); + mFamily = family; + } + + @Before + public void setUp() throws Exception { + mMockNetd = mock(INetd.class); + mMockPkgMgr = mock(PackageManager.class); + mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); + mIpSecService = new IpSecService(mTestContext, mMockIpSecSrvConfig); + + // Injecting mock netd + when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd); + + // PackageManager should always return true (feature flag tests in IpSecServiceTest) + when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true); + + // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED. + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE))) + .thenReturn(AppOpsManager.MODE_ALLOWED); + // A system package will not be granted the app op, so this should fall back to + // a permissions check, which should pass. + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE))) + .thenReturn(AppOpsManager.MODE_DEFAULT); + // A mismatch between the package name and the UID will return MODE_IGNORED. + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE))) + .thenReturn(AppOpsManager.MODE_IGNORED); + } + + //TODO: Add a test to verify SPI. + + @Test + public void testIpSecServiceReserveSpi() throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); + + IpSecSpiResponse spiResp = + mIpSecService.allocateSecurityParameterIndex( + mDestinationAddr, TEST_SPI, new Binder()); + assertEquals(IpSecManager.Status.OK, spiResp.status); + assertEquals(TEST_SPI, spiResp.spi); + } + + @Test + public void testReleaseSecurityParameterIndex() throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); + + IpSecSpiResponse spiResp = + mIpSecService.allocateSecurityParameterIndex( + mDestinationAddr, TEST_SPI, new Binder()); + + mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId); + + verify(mMockNetd) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + + // Verify quota and RefcountedResource objects cleaned up + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent); + try { + userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testSecurityParameterIndexBinderDeath() throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); + + IpSecSpiResponse spiResp = + mIpSecService.allocateSecurityParameterIndex( + mDestinationAddr, TEST_SPI, new Binder()); + + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId); + + refcountedRecord.binderDied(); + + verify(mMockNetd) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent); + try { + userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + private int getNewSpiResourceId(String remoteAddress, int returnSpi) throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), anyString(), anyInt())) + .thenReturn(returnSpi); + + IpSecSpiResponse spi = + mIpSecService.allocateSecurityParameterIndex( + InetAddresses.parseNumericAddress(remoteAddress).getHostAddress(), + IpSecManager.INVALID_SECURITY_PARAMETER_INDEX, + new Binder()); + return spi.resourceId; + } + + private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception { + config.setSpiResourceId(getNewSpiResourceId(mDestinationAddr, TEST_SPI)); + config.setSourceAddress(mSourceAddr); + config.setDestinationAddress(mDestinationAddr); + } + + private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception { + config.setEncryption(CRYPT_ALGO); + config.setAuthentication(AUTH_ALGO); + } + + private void addEncapSocketToIpSecConfig(int resourceId, IpSecConfig config) throws Exception { + config.setEncapType(IpSecTransform.ENCAP_ESPINUDP); + config.setEncapSocketResourceId(resourceId); + config.setEncapRemotePort(REMOTE_ENCAP_PORT); + } + + private void verifyTransformNetdCalledForCreatingSA( + IpSecConfig config, IpSecTransformResponse resp) throws Exception { + verifyTransformNetdCalledForCreatingSA(config, resp, 0); + } + + private void verifyTransformNetdCalledForCreatingSA( + IpSecConfig config, IpSecTransformResponse resp, int encapSocketPort) throws Exception { + IpSecAlgorithm auth = config.getAuthentication(); + IpSecAlgorithm crypt = config.getEncryption(); + IpSecAlgorithm authCrypt = config.getAuthenticatedEncryption(); + + verify(mMockNetd, times(1)) + .ipSecAddSecurityAssociation( + eq(mUid), + eq(config.getMode()), + eq(config.getSourceAddress()), + eq(config.getDestinationAddress()), + eq((config.getNetwork() != null) ? config.getNetwork().netId : 0), + eq(TEST_SPI), + eq(0), + eq(0), + eq((auth != null) ? auth.getName() : ""), + eq((auth != null) ? auth.getKey() : new byte[] {}), + eq((auth != null) ? auth.getTruncationLengthBits() : 0), + eq((crypt != null) ? crypt.getName() : ""), + eq((crypt != null) ? crypt.getKey() : new byte[] {}), + eq((crypt != null) ? crypt.getTruncationLengthBits() : 0), + eq((authCrypt != null) ? authCrypt.getName() : ""), + eq((authCrypt != null) ? authCrypt.getKey() : new byte[] {}), + eq((authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0), + eq(config.getEncapType()), + eq(encapSocketPort), + eq(config.getEncapRemotePort()), + eq(config.getXfrmInterfaceId())); + } + + @Test + public void testCreateTransform() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + @Test + public void testCreateTransformAead() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + + ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + @Test + public void testCreateTransportModeTransformWithEncap() throws Exception { + IpSecUdpEncapResponse udpSock = mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TRANSPORT); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + addEncapSocketToIpSecConfig(udpSock.resourceId, ipSecConfig); + + if (mFamily == AF_INET) { + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port); + } else { + try { + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6"); + } catch (IllegalArgumentException expected) { + } + } + } + + @Test + public void testCreateTunnelModeTransformWithEncap() throws Exception { + IpSecUdpEncapResponse udpSock = mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + addEncapSocketToIpSecConfig(udpSock.resourceId, ipSecConfig); + + if (mFamily == AF_INET) { + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port); + } else { + try { + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6"); + } catch (IllegalArgumentException expected) { + } + } + } + + @Test + public void testCreateTwoTransformsWithSameSpis() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + // Attempting to create transform a second time with the same SPIs should throw an error... + try { + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + fail("IpSecService should have thrown an error for reuse of SPI"); + } catch (IllegalStateException expected) { + } + + // ... even if the transform is deleted + mIpSecService.deleteTransform(createTransformResp.resourceId); + try { + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + fail("IpSecService should have thrown an error for reuse of SPI"); + } catch (IllegalStateException expected) { + } + } + + @Test + public void testReleaseOwnedSpi() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent); + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + verify(mMockNetd, times(0)) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + // quota is not released until the SPI is released by the Transform + assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent); + } + + @Test + public void testDeleteTransform() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + mIpSecService.deleteTransform(createTransformResp.resourceId); + + verify(mMockNetd, times(1)) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + + // Verify quota and RefcountedResource objects cleaned up + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent); + assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent); + + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + // Verify that ipSecDeleteSa was not called when the SPI was released because the + // ownedByTransform property should prevent it; (note, the called count is cumulative). + verify(mMockNetd, times(1)) + .ipSecDeleteSecurityAssociation( + anyInt(), + anyString(), + anyString(), + anyInt(), + anyInt(), + anyInt(), + anyInt()); + assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent); + + try { + userRecord.mTransformRecords.getRefcountedResourceOrThrow( + createTransformResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testTransportModeTransformBinderDeath() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mTransformRecords.getRefcountedResourceOrThrow( + createTransformResp.resourceId); + + refcountedRecord.binderDied(); + + verify(mMockNetd) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent); + try { + userRecord.mTransformRecords.getRefcountedResourceOrThrow( + createTransformResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testApplyTransportModeTransform() throws Exception { + verifyApplyTransportModeTransformCommon(false); + } + + @Test + public void testApplyTransportModeTransformReleasedSpi() throws Exception { + verifyApplyTransportModeTransformCommon(true); + } + + public void verifyApplyTransportModeTransformCommon( + boolean closeSpiBeforeApply) throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + + if (closeSpiBeforeApply) { + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + } + + Socket socket = new Socket(); + socket.bind(null); + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + + int resourceId = createTransformResp.resourceId; + mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId); + + verify(mMockNetd) + .ipSecApplyTransportModeTransform( + eq(pfd), + eq(mUid), + eq(IpSecManager.DIRECTION_OUT), + anyString(), + anyString(), + eq(TEST_SPI)); + } + + @Test + public void testApplyTransportModeTransformWithClosedSpi() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + + // Close SPI record + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + + Socket socket = new Socket(); + socket.bind(null); + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + + int resourceId = createTransformResp.resourceId; + mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId); + + verify(mMockNetd) + .ipSecApplyTransportModeTransform( + eq(pfd), + eq(mUid), + eq(IpSecManager.DIRECTION_OUT), + anyString(), + anyString(), + eq(TEST_SPI)); + } + + @Test + public void testRemoveTransportModeTransform() throws Exception { + Socket socket = new Socket(); + socket.bind(null); + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + mIpSecService.removeTransportModeTransforms(pfd); + + verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd); + } + + private IpSecTunnelInterfaceResponse createAndValidateTunnel( + String localAddr, String remoteAddr, String pkgName) throws Exception { + final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel(); + config.flags = new String[] {IF_STATE_DOWN}; + when(mMockNetd.interfaceGetCfg(anyString())).thenReturn(config); + IpSecTunnelInterfaceResponse createTunnelResp = + mIpSecService.createTunnelInterface( + mSourceAddr, mDestinationAddr, fakeNetwork, new Binder(), pkgName); + + assertNotNull(createTunnelResp); + assertEquals(IpSecManager.Status.OK, createTunnelResp.status); + for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT, DIRECTION_FWD}) { + for (int selAddrFamily : ADDRESS_FAMILIES) { + verify(mMockNetd).ipSecAddSecurityPolicy( + eq(mUid), + eq(selAddrFamily), + eq(direction), + anyString(), + anyString(), + eq(0), + anyInt(), // iKey/oKey + anyInt(), // mask + eq(createTunnelResp.resourceId)); + } + } + + return createTunnelResp; + } + + @Test + public void testCreateTunnelInterface() throws Exception { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + // Check that we have stored the tracking object, and retrieve it + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow( + createTunnelResp.resourceId); + + assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent); + verify(mMockNetd) + .ipSecAddTunnelInterface( + eq(createTunnelResp.interfaceName), + eq(mSourceAddr), + eq(mDestinationAddr), + anyInt(), + anyInt(), + anyInt()); + verify(mMockNetd).interfaceSetCfg(argThat( + config -> Arrays.asList(config.flags).contains(IF_STATE_UP))); + } + + @Test + public void testDeleteTunnelInterface() throws Exception { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + + mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent); + verify(mMockNetd).ipSecRemoveTunnelInterface(eq(createTunnelResp.interfaceName)); + try { + userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow( + createTunnelResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + } + } + + private Network createFakeUnderlyingNetwork(String interfaceName) { + final Network fakeNetwork = new Network(1000); + final LinkProperties fakeLp = new LinkProperties(); + fakeLp.setInterfaceName(interfaceName); + when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp); + return fakeNetwork; + } + + @Test + public void testSetNetworkForTunnelInterface() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface"); + final int tunnelIfaceResourceId = createTunnelResp.resourceId; + mIpSecService.setNetworkForTunnelInterface( + tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE); + + final IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent); + + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId); + assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork()); + } + + @Test + public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final Network newFakeNetwork = new Network(1000); + + try { + mIpSecService.setNetworkForTunnelInterface( + IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE); + fail("Expected an IllegalArgumentException for invalid resource ID."); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final int tunnelIfaceResourceId = createTunnelResp.resourceId; + final IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(mUid); + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId); + + final Network newFakeNetwork = + createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName()); + + try { + mIpSecService.setNetworkForTunnelInterface( + tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE); + fail( + "Expected an IllegalArgumentException because the underlying network is the" + + " network being exposed by this tunnel."); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testTunnelInterfaceBinderDeath() throws Exception { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow( + createTunnelResp.resourceId); + + refcountedRecord.binderDied(); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent); + verify(mMockNetd).ipSecRemoveTunnelInterface(eq(createTunnelResp.interfaceName)); + try { + userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow( + createTunnelResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testApplyTunnelModeTransformOutbound() throws Exception { + verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT); + } + + @Test + public void testApplyTunnelModeTransformOutboundNonNetworkStack() throws Exception { + mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS); + verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT); + } + + @Test + public void testApplyTunnelModeTransformOutboundReleasedSpi() throws Exception { + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_OUT); + } + + @Test + public void testApplyTunnelModeTransformInbound() throws Exception { + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN); + } + + @Test + public void testApplyTunnelModeTransformInboundNonNetworkStack() throws Exception { + mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS); + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN); + } + + @Test + public void testApplyTunnelModeTransformForward() throws Exception { + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD); + } + + @Test + public void testApplyTunnelModeTransformForwardNonNetworkStack() throws Exception { + mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS); + + try { + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD); + fail("Expected security exception due to use of forward policies without NETWORK_STACK" + + " or MAINLINE_NETWORK_STACK permission"); + } catch (SecurityException expected) { + } + } + + public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply, int direction) + throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + if (closeSpiBeforeApply) { + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + } + + int transformResourceId = createTransformResp.resourceId; + int tunnelResourceId = createTunnelResp.resourceId; + mIpSecService.applyTunnelModeTransform( + tunnelResourceId, direction, transformResourceId, BLESSED_PACKAGE); + + for (int selAddrFamily : ADDRESS_FAMILIES) { + verify(mMockNetd) + .ipSecUpdateSecurityPolicy( + eq(mUid), + eq(selAddrFamily), + eq(direction), + anyString(), + anyString(), + eq(direction == DIRECTION_OUT ? TEST_SPI : 0), + anyInt(), // iKey/oKey + anyInt(), // mask + eq(tunnelResourceId)); + } + + ipSecConfig.setXfrmInterfaceId(tunnelResourceId); + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + + @Test + public void testApplyTunnelModeTransformWithClosedSpi() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + // Close SPI record + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + + int transformResourceId = createTransformResp.resourceId; + int tunnelResourceId = createTunnelResp.resourceId; + mIpSecService.applyTunnelModeTransform( + tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE); + + for (int selAddrFamily : ADDRESS_FAMILIES) { + verify(mMockNetd) + .ipSecUpdateSecurityPolicy( + eq(mUid), + eq(selAddrFamily), + eq(IpSecManager.DIRECTION_OUT), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), // iKey/oKey + anyInt(), // mask + eq(tunnelResourceId)); + } + + ipSecConfig.setXfrmInterfaceId(tunnelResourceId); + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + @Test + public void testAddRemoveAddressFromTunnelInterface() throws Exception { + for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName); + mIpSecService.addAddressToTunnelInterface( + createTunnelResp.resourceId, mLocalInnerAddress, pkgName); + verify(mMockNetd, times(1)) + .interfaceAddAddress( + eq(createTunnelResp.interfaceName), + eq(mLocalInnerAddress.getAddress().getHostAddress()), + eq(mLocalInnerAddress.getPrefixLength())); + mIpSecService.removeAddressFromTunnelInterface( + createTunnelResp.resourceId, mLocalInnerAddress, pkgName); + verify(mMockNetd, times(1)) + .interfaceDelAddress( + eq(createTunnelResp.interfaceName), + eq(mLocalInnerAddress.getAddress().getHostAddress()), + eq(mLocalInnerAddress.getPrefixLength())); + mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, pkgName); + } + } + + @Ignore + @Test + public void testAddTunnelFailsForBadPackageName() throws Exception { + try { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE); + fail("Expected a SecurityException for badPackage."); + } catch (SecurityException expected) { + } + } + + @Test + public void testFeatureFlagVerification() throws Exception { + when(mMockPkgMgr.hasSystemFeature(eq(PackageManager.FEATURE_IPSEC_TUNNELS))) + .thenReturn(false); + + try { + String addr = Inet4Address.getLoopbackAddress().getHostAddress(); + mIpSecService.createTunnelInterface( + addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE); + fail("Expected UnsupportedOperationException for disabled feature"); + } catch (UnsupportedOperationException expected) { + } + } +} diff --git a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java new file mode 100644 index 0000000000..22a2c94fc1 --- /dev/null +++ b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.IpSecService.IResource; +import com.android.server.IpSecService.RefcountedResource; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +/** Unit tests for {@link IpSecService.RefcountedResource}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class IpSecServiceRefcountedResourceTest { + Context mMockContext; + IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; + IpSecService mIpSecService; + + @Before + public void setUp() throws Exception { + mMockContext = mock(Context.class); + mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); + mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); + } + + private void assertResourceState( + RefcountedResource resource, + int refCount, + int userReleaseCallCount, + int releaseReferenceCallCount, + int invalidateCallCount, + int freeUnderlyingResourcesCallCount) + throws RemoteException { + // Check refcount on RefcountedResource + assertEquals(refCount, resource.mRefCount); + + // Check call count of RefcountedResource + verify(resource, times(userReleaseCallCount)).userRelease(); + verify(resource, times(releaseReferenceCallCount)).releaseReference(); + + // Check call count of IResource + verify(resource.getResource(), times(invalidateCallCount)).invalidate(); + verify(resource.getResource(), times(freeUnderlyingResourcesCallCount)) + .freeUnderlyingResources(); + } + + /** Adds mockito instrumentation */ + private RefcountedResource getTestRefcountedResource( + RefcountedResource... children) { + return getTestRefcountedResource(new Binder(), children); + } + + /** Adds mockito instrumentation with provided binder */ + private RefcountedResource getTestRefcountedResource( + IBinder binder, RefcountedResource... children) { + return spy( + mIpSecService + .new RefcountedResource(mock(IResource.class), binder, children)); + } + + @Test + public void testConstructor() throws RemoteException { + IBinder binderMock = mock(IBinder.class); + RefcountedResource resource = getTestRefcountedResource(binderMock); + + // Verify resource's refcount starts at 1 (for user-reference) + assertResourceState(resource, 1, 0, 0, 0, 0); + + // Verify linking to binder death + verify(binderMock).linkToDeath(anyObject(), anyInt()); + } + + @Test + public void testConstructorWithChildren() throws RemoteException { + IBinder binderMockChild = mock(IBinder.class); + IBinder binderMockParent = mock(IBinder.class); + RefcountedResource childResource = getTestRefcountedResource(binderMockChild); + RefcountedResource parentResource = + getTestRefcountedResource(binderMockParent, childResource); + + // Verify parent's refcount starts at 1 (for user-reference) + assertResourceState(parentResource, 1, 0, 0, 0, 0); + + // Verify child's refcounts were incremented + assertResourceState(childResource, 2, 0, 0, 0, 0); + + // Verify linking to binder death + verify(binderMockChild).linkToDeath(anyObject(), anyInt()); + verify(binderMockParent).linkToDeath(anyObject(), anyInt()); + } + + @Test + public void testFailLinkToDeath() throws RemoteException { + IBinder binderMock = mock(IBinder.class); + doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt()); + + try { + getTestRefcountedResource(binderMock); + fail("Expected exception to propogate when binder fails to link to death"); + } catch (RuntimeException expected) { + } + } + + @Test + public void testCleanupAndRelease() throws RemoteException { + IBinder binderMock = mock(IBinder.class); + RefcountedResource refcountedResource = getTestRefcountedResource(binderMock); + + // Verify user-initiated cleanup path decrements refcount and calls full cleanup flow + refcountedResource.userRelease(); + assertResourceState(refcountedResource, -1, 1, 1, 1, 1); + + // Verify user-initated cleanup path unlinks from binder + verify(binderMock).unlinkToDeath(eq(refcountedResource), eq(0)); + assertNull(refcountedResource.mBinder); + } + + @Test + public void testMultipleCallsToCleanupAndRelease() throws RemoteException { + RefcountedResource refcountedResource = getTestRefcountedResource(); + + // Verify calling userRelease multiple times does not trigger any other cleanup + // methods + refcountedResource.userRelease(); + assertResourceState(refcountedResource, -1, 1, 1, 1, 1); + + refcountedResource.userRelease(); + refcountedResource.userRelease(); + assertResourceState(refcountedResource, -1, 3, 1, 1, 1); + } + + @Test + public void testBinderDeathAfterCleanupAndReleaseDoesNothing() throws RemoteException { + RefcountedResource refcountedResource = getTestRefcountedResource(); + + refcountedResource.userRelease(); + assertResourceState(refcountedResource, -1, 1, 1, 1, 1); + + // Verify binder death call does not trigger any other cleanup methods if called after + // userRelease() + refcountedResource.binderDied(); + assertResourceState(refcountedResource, -1, 2, 1, 1, 1); + } + + @Test + public void testBinderDeath() throws RemoteException { + RefcountedResource refcountedResource = getTestRefcountedResource(); + + // Verify binder death caused cleanup + refcountedResource.binderDied(); + verify(refcountedResource, times(1)).binderDied(); + assertResourceState(refcountedResource, -1, 1, 1, 1, 1); + assertNull(refcountedResource.mBinder); + } + + @Test + public void testCleanupParentDecrementsChildRefcount() throws RemoteException { + RefcountedResource childResource = getTestRefcountedResource(); + RefcountedResource parentResource = getTestRefcountedResource(childResource); + + parentResource.userRelease(); + + // Verify parent gets cleaned up properly, and triggers releaseReference on + // child + assertResourceState(childResource, 1, 0, 1, 0, 0); + assertResourceState(parentResource, -1, 1, 1, 1, 1); + } + + @Test + public void testCleanupReferencedChildDoesNotTriggerRelease() throws RemoteException { + RefcountedResource childResource = getTestRefcountedResource(); + RefcountedResource parentResource = getTestRefcountedResource(childResource); + + childResource.userRelease(); + + // Verify that child does not clean up kernel resources and quota. + assertResourceState(childResource, 1, 1, 1, 1, 0); + assertResourceState(parentResource, 1, 0, 0, 0, 0); + } + + @Test + public void testTwoParents() throws RemoteException { + RefcountedResource childResource = getTestRefcountedResource(); + RefcountedResource parentResource1 = getTestRefcountedResource(childResource); + RefcountedResource parentResource2 = getTestRefcountedResource(childResource); + + // Verify that child does not cleanup kernel resources and quota until all references + // have been released. Assumption: parents release correctly based on + // testCleanupParentDecrementsChildRefcount() + childResource.userRelease(); + assertResourceState(childResource, 2, 1, 1, 1, 0); + + parentResource1.userRelease(); + assertResourceState(childResource, 1, 1, 2, 1, 0); + + parentResource2.userRelease(); + assertResourceState(childResource, -1, 1, 3, 1, 1); + } + + @Test + public void testTwoChildren() throws RemoteException { + RefcountedResource childResource1 = getTestRefcountedResource(); + RefcountedResource childResource2 = getTestRefcountedResource(); + RefcountedResource parentResource = + getTestRefcountedResource(childResource1, childResource2); + + childResource1.userRelease(); + assertResourceState(childResource1, 1, 1, 1, 1, 0); + assertResourceState(childResource2, 2, 0, 0, 0, 0); + + parentResource.userRelease(); + assertResourceState(childResource1, -1, 1, 2, 1, 1); + assertResourceState(childResource2, 1, 0, 1, 0, 0); + + childResource2.userRelease(); + assertResourceState(childResource1, -1, 1, 2, 1, 1); + assertResourceState(childResource2, -1, 1, 2, 1, 1); + } + + @Test + public void testSampleUdpEncapTranform() throws RemoteException { + RefcountedResource spi1 = getTestRefcountedResource(); + RefcountedResource spi2 = getTestRefcountedResource(); + RefcountedResource udpEncapSocket = getTestRefcountedResource(); + RefcountedResource transform = + getTestRefcountedResource(spi1, spi2, udpEncapSocket); + + // Pretend one SPI goes out of reference (releaseManagedResource -> userRelease) + spi1.userRelease(); + + // User called releaseManagedResource on udpEncap socket + udpEncapSocket.userRelease(); + + // User dies, and binder kills the rest + spi2.binderDied(); + transform.binderDied(); + + // Check resource states + assertResourceState(spi1, -1, 1, 2, 1, 1); + assertResourceState(spi2, -1, 1, 2, 1, 1); + assertResourceState(udpEncapSocket, -1, 1, 2, 1, 1); + assertResourceState(transform, -1, 1, 1, 1, 1); + } + + @Test + public void testSampleDualTransformEncapSocket() throws RemoteException { + RefcountedResource spi1 = getTestRefcountedResource(); + RefcountedResource spi2 = getTestRefcountedResource(); + RefcountedResource spi3 = getTestRefcountedResource(); + RefcountedResource spi4 = getTestRefcountedResource(); + RefcountedResource udpEncapSocket = getTestRefcountedResource(); + RefcountedResource transform1 = + getTestRefcountedResource(spi1, spi2, udpEncapSocket); + RefcountedResource transform2 = + getTestRefcountedResource(spi3, spi4, udpEncapSocket); + + // Pretend one SPIs goes out of reference (releaseManagedResource -> userRelease) + spi1.userRelease(); + + // User called releaseManagedResource on udpEncap socket and spi4 + udpEncapSocket.userRelease(); + spi4.userRelease(); + + // User dies, and binder kills the rest + spi2.binderDied(); + spi3.binderDied(); + transform2.binderDied(); + transform1.binderDied(); + + // Check resource states + assertResourceState(spi1, -1, 1, 2, 1, 1); + assertResourceState(spi2, -1, 1, 2, 1, 1); + assertResourceState(spi3, -1, 1, 2, 1, 1); + assertResourceState(spi4, -1, 1, 2, 1, 1); + assertResourceState(udpEncapSocket, -1, 1, 3, 1, 1); + assertResourceState(transform1, -1, 1, 1, 1, 1); + assertResourceState(transform2, -1, 1, 1, 1, 1); + } + + @Test + public void fuzzTest() throws RemoteException { + List> resources = new ArrayList<>(); + + // Build a tree of resources + for (int i = 0; i < 100; i++) { + // Choose a random number of children from the existing list + int numChildren = ThreadLocalRandom.current().nextInt(0, resources.size() + 1); + + // Build a (random) list of children + Set> children = new HashSet<>(); + for (int j = 0; j < numChildren; j++) { + int childIndex = ThreadLocalRandom.current().nextInt(0, resources.size()); + children.add(resources.get(childIndex)); + } + + RefcountedResource newRefcountedResource = + getTestRefcountedResource( + children.toArray(new RefcountedResource[children.size()])); + resources.add(newRefcountedResource); + } + + // Cleanup all resources in a random order + List> clonedResources = + new ArrayList<>(resources); // shallow copy + while (!clonedResources.isEmpty()) { + int index = ThreadLocalRandom.current().nextInt(0, clonedResources.size()); + RefcountedResource refcountedResource = clonedResources.get(index); + refcountedResource.userRelease(); + clonedResources.remove(index); + } + + // Verify all resources were cleaned up properly + for (RefcountedResource refcountedResource : resources) { + assertEquals(-1, refcountedResource.mRefCount); + } + } +} diff --git a/tests/unit/java/com/android/server/IpSecServiceTest.java b/tests/unit/java/com/android/server/IpSecServiceTest.java new file mode 100644 index 0000000000..6232423b4f --- /dev/null +++ b/tests/unit/java/com/android/server/IpSecServiceTest.java @@ -0,0 +1,670 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.EADDRINUSE; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.INetd; +import android.net.IpSecAlgorithm; +import android.net.IpSecConfig; +import android.net.IpSecManager; +import android.net.IpSecSpiResponse; +import android.net.IpSecUdpEncapResponse; +import android.os.Binder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; +import android.util.Range; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import dalvik.system.SocketTagger; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +/** Unit tests for {@link IpSecService}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class IpSecServiceTest { + + private static final int DROID_SPI = 0xD1201D; + private static final int MAX_NUM_ENCAP_SOCKETS = 100; + private static final int MAX_NUM_SPIS = 100; + private static final int TEST_UDP_ENCAP_INVALID_PORT = 100; + private static final int TEST_UDP_ENCAP_PORT_OUT_RANGE = 100000; + + private static final InetAddress INADDR_ANY; + + private static final byte[] AEAD_KEY = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x73, 0x61, 0x6C, 0x74 + }; + private static final byte[] CRYPT_KEY = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + private static final byte[] AUTH_KEY = { + 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, + 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F + }; + + private static final IpSecAlgorithm AUTH_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4); + private static final IpSecAlgorithm CRYPT_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY); + private static final IpSecAlgorithm AEAD_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); + + static { + try { + INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0}); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + Context mMockContext; + INetd mMockNetd; + IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; + IpSecService mIpSecService; + + @Before + public void setUp() throws Exception { + mMockContext = mock(Context.class); + mMockNetd = mock(INetd.class); + mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); + mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig); + + // Injecting mock netd + when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd); + } + + @Test + public void testIpSecServiceCreate() throws InterruptedException { + IpSecService ipSecSrv = IpSecService.create(mMockContext); + assertNotNull(ipSecSrv); + } + + @Test + public void testReleaseInvalidSecurityParameterIndex() throws Exception { + try { + mIpSecService.releaseSecurityParameterIndex(1); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + /** This function finds an available port */ + int findUnusedPort() throws Exception { + // Get an available port. + ServerSocket s = new ServerSocket(0); + int port = s.getLocalPort(); + s.close(); + return port; + } + + @Test + public void testOpenAndCloseUdpEncapsulationSocket() throws Exception { + int localport = -1; + IpSecUdpEncapResponse udpEncapResp = null; + + for (int i = 0; i < IpSecService.MAX_PORT_BIND_ATTEMPTS; i++) { + localport = findUnusedPort(); + + udpEncapResp = mIpSecService.openUdpEncapsulationSocket(localport, new Binder()); + assertNotNull(udpEncapResp); + if (udpEncapResp.status == IpSecManager.Status.OK) { + break; + } + + // Else retry to reduce possibility for port-bind failures. + } + + assertNotNull(udpEncapResp); + assertEquals(IpSecManager.Status.OK, udpEncapResp.status); + assertEquals(localport, udpEncapResp.port); + + mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); + udpEncapResp.fileDescriptor.close(); + + // Verify quota and RefcountedResource objects cleaned up + IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid()); + assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent); + try { + userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testUdpEncapsulationSocketBinderDeath() throws Exception { + IpSecUdpEncapResponse udpEncapResp = + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + + IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid()); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow( + udpEncapResp.resourceId); + + refcountedRecord.binderDied(); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent); + try { + userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testOpenUdpEncapsulationSocketAfterClose() throws Exception { + IpSecUdpEncapResponse udpEncapResp = + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(udpEncapResp); + assertEquals(IpSecManager.Status.OK, udpEncapResp.status); + int localport = udpEncapResp.port; + + mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); + udpEncapResp.fileDescriptor.close(); + + /** Check if localport is available. */ + FileDescriptor newSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + Os.bind(newSocket, INADDR_ANY, localport); + Os.close(newSocket); + } + + /** + * This function checks if the IpSecService holds the reserved port. If + * closeUdpEncapsulationSocket is not called, the socket cleanup should not be complete. + */ + @Test + public void testUdpEncapPortNotReleased() throws Exception { + IpSecUdpEncapResponse udpEncapResp = + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(udpEncapResp); + assertEquals(IpSecManager.Status.OK, udpEncapResp.status); + int localport = udpEncapResp.port; + + udpEncapResp.fileDescriptor.close(); + + FileDescriptor newSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + try { + Os.bind(newSocket, INADDR_ANY, localport); + fail("ErrnoException not thrown"); + } catch (ErrnoException e) { + assertEquals(EADDRINUSE, e.errno); + } + mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); + } + + @Test + public void testOpenUdpEncapsulationSocketOnRandomPort() throws Exception { + IpSecUdpEncapResponse udpEncapResp = + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(udpEncapResp); + assertEquals(IpSecManager.Status.OK, udpEncapResp.status); + assertNotEquals(0, udpEncapResp.port); + mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); + udpEncapResp.fileDescriptor.close(); + } + + @Test + public void testOpenUdpEncapsulationSocketPortRange() throws Exception { + try { + mIpSecService.openUdpEncapsulationSocket(TEST_UDP_ENCAP_INVALID_PORT, new Binder()); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + + try { + mIpSecService.openUdpEncapsulationSocket(TEST_UDP_ENCAP_PORT_OUT_RANGE, new Binder()); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testOpenUdpEncapsulationSocketTwice() throws Exception { + IpSecUdpEncapResponse udpEncapResp = + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(udpEncapResp); + assertEquals(IpSecManager.Status.OK, udpEncapResp.status); + int localport = udpEncapResp.port; + + IpSecUdpEncapResponse testUdpEncapResp = + mIpSecService.openUdpEncapsulationSocket(localport, new Binder()); + assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, testUdpEncapResp.status); + + mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); + udpEncapResp.fileDescriptor.close(); + } + + @Test + public void testCloseInvalidUdpEncapsulationSocket() throws Exception { + try { + mIpSecService.closeUdpEncapsulationSocket(1); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testValidateAlgorithmsAuth() { + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthentication(AUTH_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) { + try { + config = new IpSecConfig(); + config.setAuthentication(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { + } + } + } + + @Test + public void testValidateAlgorithmsCrypt() { + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setEncryption(CRYPT_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) { + try { + config = new IpSecConfig(); + config.setEncryption(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { + } + } + } + + @Test + public void testValidateAlgorithmsAead() { + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthenticatedEncryption(AEAD_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) { + try { + config = new IpSecConfig(); + config.setAuthenticatedEncryption(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { + } + } + } + + @Test + public void testValidateAlgorithmsAuthCrypt() { + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthentication(AUTH_ALGO); + config.setEncryption(CRYPT_ALGO); + mIpSecService.validateAlgorithms(config); + } + + @Test + public void testValidateAlgorithmsNoAlgorithms() { + IpSecConfig config = new IpSecConfig(); + try { + mIpSecService.validateAlgorithms(config); + fail("Expected exception; no algorithms specified"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testValidateAlgorithmsAeadWithAuth() { + IpSecConfig config = new IpSecConfig(); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setAuthentication(AUTH_ALGO); + try { + mIpSecService.validateAlgorithms(config); + fail("Expected exception; both AEAD and auth algorithm specified"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testValidateAlgorithmsAeadWithCrypt() { + IpSecConfig config = new IpSecConfig(); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setEncryption(CRYPT_ALGO); + try { + mIpSecService.validateAlgorithms(config); + fail("Expected exception; both AEAD and crypt algorithm specified"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testValidateAlgorithmsAeadWithAuthAndCrypt() { + IpSecConfig config = new IpSecConfig(); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setAuthentication(AUTH_ALGO); + config.setEncryption(CRYPT_ALGO); + try { + mIpSecService.validateAlgorithms(config); + fail("Expected exception; AEAD, auth and crypt algorithm specified"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testDeleteInvalidTransform() throws Exception { + try { + mIpSecService.deleteTransform(1); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testRemoveTransportModeTransform() throws Exception { + Socket socket = new Socket(); + socket.bind(null); + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + mIpSecService.removeTransportModeTransforms(pfd); + + verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd); + } + + @Test + public void testValidateIpAddresses() throws Exception { + String[] invalidAddresses = + new String[] {"www.google.com", "::", "2001::/64", "0.0.0.0", ""}; + for (String address : invalidAddresses) { + try { + IpSecSpiResponse spiResp = + mIpSecService.allocateSecurityParameterIndex( + address, DROID_SPI, new Binder()); + fail("Invalid address was passed through IpSecService validation: " + address); + } catch (IllegalArgumentException e) { + } catch (Exception e) { + fail( + "Invalid InetAddress was not caught in validation: " + + address + + ", Exception: " + + e); + } + } + } + + /** + * This function checks if the number of encap UDP socket that one UID can reserve has a + * reasonable limit. + */ + @Test + public void testSocketResourceTrackerLimitation() throws Exception { + List openUdpEncapSockets = new ArrayList(); + // Reserve sockets until it fails. + for (int i = 0; i < MAX_NUM_ENCAP_SOCKETS; i++) { + IpSecUdpEncapResponse newUdpEncapSocket = + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(newUdpEncapSocket); + if (IpSecManager.Status.OK != newUdpEncapSocket.status) { + break; + } + openUdpEncapSockets.add(newUdpEncapSocket); + } + // Assert that the total sockets quota has a reasonable limit. + assertTrue("No UDP encap socket was open", !openUdpEncapSockets.isEmpty()); + assertTrue( + "Number of open UDP encap sockets is out of bound", + openUdpEncapSockets.size() < MAX_NUM_ENCAP_SOCKETS); + + // Try to reserve one more UDP encapsulation socket, and should fail. + IpSecUdpEncapResponse extraUdpEncapSocket = + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(extraUdpEncapSocket); + assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, extraUdpEncapSocket.status); + + // Close one of the open UDP encapsulation sockets. + mIpSecService.closeUdpEncapsulationSocket(openUdpEncapSockets.get(0).resourceId); + openUdpEncapSockets.get(0).fileDescriptor.close(); + openUdpEncapSockets.remove(0); + + // Try to reserve one more UDP encapsulation socket, and should be successful. + extraUdpEncapSocket = mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(extraUdpEncapSocket); + assertEquals(IpSecManager.Status.OK, extraUdpEncapSocket.status); + openUdpEncapSockets.add(extraUdpEncapSocket); + + // Close open UDP sockets. + for (IpSecUdpEncapResponse openSocket : openUdpEncapSockets) { + mIpSecService.closeUdpEncapsulationSocket(openSocket.resourceId); + openSocket.fileDescriptor.close(); + } + } + + /** + * This function checks if the number of SPI that one UID can reserve has a reasonable limit. + * This test does not test for both address families or duplicate SPIs because resource tracking + * code does not depend on them. + */ + @Test + public void testSpiResourceTrackerLimitation() throws Exception { + List reservedSpis = new ArrayList(); + // Return the same SPI for all SPI allocation since IpSecService only + // tracks the resource ID. + when(mMockNetd.ipSecAllocateSpi( + anyInt(), + anyString(), + eq(InetAddress.getLoopbackAddress().getHostAddress()), + anyInt())) + .thenReturn(DROID_SPI); + // Reserve spis until it fails. + for (int i = 0; i < MAX_NUM_SPIS; i++) { + IpSecSpiResponse newSpi = + mIpSecService.allocateSecurityParameterIndex( + InetAddress.getLoopbackAddress().getHostAddress(), + DROID_SPI + i, + new Binder()); + assertNotNull(newSpi); + if (IpSecManager.Status.OK != newSpi.status) { + break; + } + reservedSpis.add(newSpi); + } + // Assert that the SPI quota has a reasonable limit. + assertTrue(reservedSpis.size() > 0 && reservedSpis.size() < MAX_NUM_SPIS); + + // Try to reserve one more SPI, and should fail. + IpSecSpiResponse extraSpi = + mIpSecService.allocateSecurityParameterIndex( + InetAddress.getLoopbackAddress().getHostAddress(), + DROID_SPI + MAX_NUM_SPIS, + new Binder()); + assertNotNull(extraSpi); + assertEquals(IpSecManager.Status.RESOURCE_UNAVAILABLE, extraSpi.status); + + // Release one reserved spi. + mIpSecService.releaseSecurityParameterIndex(reservedSpis.get(0).resourceId); + reservedSpis.remove(0); + + // Should successfully reserve one more spi. + extraSpi = + mIpSecService.allocateSecurityParameterIndex( + InetAddress.getLoopbackAddress().getHostAddress(), + DROID_SPI + MAX_NUM_SPIS, + new Binder()); + assertNotNull(extraSpi); + assertEquals(IpSecManager.Status.OK, extraSpi.status); + + // Release reserved SPIs. + for (IpSecSpiResponse spiResp : reservedSpis) { + mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId); + } + } + + @Test + public void testUidFdtagger() throws Exception { + SocketTagger actualSocketTagger = SocketTagger.get(); + + try { + FileDescriptor sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + // Has to be done after socket creation because BlockGuardOS calls tag on new sockets + SocketTagger mockSocketTagger = mock(SocketTagger.class); + SocketTagger.set(mockSocketTagger); + + mIpSecService.mUidFdTagger.tag(sockFd, Process.LAST_APPLICATION_UID); + verify(mockSocketTagger).tag(eq(sockFd)); + } finally { + SocketTagger.set(actualSocketTagger); + } + } + + /** + * Checks if two file descriptors point to the same file. + * + *

      According to stat.h documentation, the correct way to check for equivalent or duplicated + * file descriptors is to check their inode and device. These two entries uniquely identify any + * file. + */ + private boolean fileDescriptorsEqual(FileDescriptor fd1, FileDescriptor fd2) { + try { + StructStat fd1Stat = Os.fstat(fd1); + StructStat fd2Stat = Os.fstat(fd2); + + return fd1Stat.st_ino == fd2Stat.st_ino && fd1Stat.st_dev == fd2Stat.st_dev; + } catch (ErrnoException e) { + return false; + } + } + + @Test + public void testOpenUdpEncapSocketTagsSocket() throws Exception { + IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class); + IpSecService testIpSecService = new IpSecService( + mMockContext, mMockIpSecSrvConfig, mockTagger); + + IpSecUdpEncapResponse udpEncapResp = + testIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(udpEncapResp); + assertEquals(IpSecManager.Status.OK, udpEncapResp.status); + + FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor(); + ArgumentMatcher fdMatcher = + (argFd) -> { + return fileDescriptorsEqual(sockFd, argFd); + }; + verify(mockTagger).tag(argThat(fdMatcher), eq(Os.getuid())); + + testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); + udpEncapResp.fileDescriptor.close(); + } + + @Test + public void testOpenUdpEncapsulationSocketCallsSetEncapSocketOwner() throws Exception { + IpSecUdpEncapResponse udpEncapResp = + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + + FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor(); + ArgumentMatcher fdMatcher = (arg) -> { + try { + StructStat sockStat = Os.fstat(sockFd); + StructStat argStat = Os.fstat(arg.getFileDescriptor()); + + return sockStat.st_ino == argStat.st_ino + && sockStat.st_dev == argStat.st_dev; + } catch (ErrnoException e) { + return false; + } + }; + + verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid())); + mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); + } + + @Test + public void testReserveNetId() { + final Range netIdRange = ConnectivityManager.getIpSecNetIdRange(); + for (int netId = netIdRange.getLower(); netId <= netIdRange.getUpper(); netId++) { + assertEquals(netId, mIpSecService.reserveNetId()); + } + + // Check that resource exhaustion triggers an exception + try { + mIpSecService.reserveNetId(); + fail("Did not throw error for all netIds reserved"); + } catch (IllegalStateException expected) { + } + + // Now release one and try again + int releasedNetId = + netIdRange.getLower() + (netIdRange.getUpper() - netIdRange.getLower()) / 2; + mIpSecService.releaseNetId(releasedNetId); + assertEquals(releasedNetId, mIpSecService.reserveNetId()); + } +} diff --git a/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt new file mode 100644 index 0000000000..5ec111954f --- /dev/null +++ b/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt @@ -0,0 +1,197 @@ +/* + * 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. + */ + +// Don't warn about deprecated types anywhere in this test, because LegacyTypeTracker's very reason +// for existence is to power deprecated APIs. The annotation has to apply to the whole file because +// otherwise warnings will be generated by the imports of deprecated constants like TYPE_xxx. +@file:Suppress("DEPRECATION") + +package com.android.server + +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.PackageManager.FEATURE_WIFI +import android.content.pm.PackageManager.FEATURE_WIFI_DIRECT +import android.net.ConnectivityManager.TYPE_ETHERNET +import android.net.ConnectivityManager.TYPE_MOBILE +import android.net.ConnectivityManager.TYPE_MOBILE_CBS +import android.net.ConnectivityManager.TYPE_MOBILE_DUN +import android.net.ConnectivityManager.TYPE_MOBILE_EMERGENCY +import android.net.ConnectivityManager.TYPE_MOBILE_FOTA +import android.net.ConnectivityManager.TYPE_MOBILE_HIPRI +import android.net.ConnectivityManager.TYPE_MOBILE_IA +import android.net.ConnectivityManager.TYPE_MOBILE_IMS +import android.net.ConnectivityManager.TYPE_MOBILE_MMS +import android.net.ConnectivityManager.TYPE_MOBILE_SUPL +import android.net.ConnectivityManager.TYPE_VPN +import android.net.ConnectivityManager.TYPE_WIFI +import android.net.ConnectivityManager.TYPE_WIFI_P2P +import android.net.ConnectivityManager.TYPE_WIMAX +import android.net.EthernetManager +import android.net.NetworkInfo.DetailedState.CONNECTED +import android.net.NetworkInfo.DetailedState.DISCONNECTED +import android.telephony.TelephonyManager +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.server.ConnectivityService.LegacyTypeTracker +import com.android.server.connectivity.NetworkAgentInfo +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify + +const val UNSUPPORTED_TYPE = TYPE_WIMAX + +@RunWith(AndroidJUnit4::class) +@SmallTest +class LegacyTypeTrackerTest { + private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE, + TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI, + TYPE_MOBILE_FOTA, TYPE_MOBILE_IMS, TYPE_MOBILE_CBS, TYPE_MOBILE_IA, + TYPE_MOBILE_EMERGENCY, TYPE_VPN) + + private val mMockService = mock(ConnectivityService::class.java).apply { + doReturn(false).`when`(this).isDefaultNetwork(any()) + } + private val mPm = mock(PackageManager::class.java) + private val mContext = mock(Context::class.java).apply { + doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI) + doReturn(true).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT) + doReturn(mPm).`when`(this).packageManager + doReturn(mock(EthernetManager::class.java)).`when`(this).getSystemService( + Context.ETHERNET_SERVICE) + } + private val mTm = mock(TelephonyManager::class.java).apply { + doReturn(true).`when`(this).isDataCapable + } + + private fun makeTracker() = LegacyTypeTracker(mMockService).apply { + loadSupportedTypes(mContext, mTm) + } + + @Test + fun testSupportedTypes() { + val tracker = makeTracker() + supportedTypes.forEach { + assertTrue(tracker.isTypeSupported(it)) + } + assertFalse(tracker.isTypeSupported(UNSUPPORTED_TYPE)) + } + + @Test + fun testSupportedTypes_NoEthernet() { + doReturn(null).`when`(mContext).getSystemService(Context.ETHERNET_SERVICE) + assertFalse(makeTracker().isTypeSupported(TYPE_ETHERNET)) + } + + @Test + fun testSupportedTypes_NoTelephony() { + doReturn(false).`when`(mTm).isDataCapable + val tracker = makeTracker() + val nonMobileTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_VPN) + nonMobileTypes.forEach { + assertTrue(tracker.isTypeSupported(it)) + } + supportedTypes.toSet().minus(nonMobileTypes).forEach { + assertFalse(tracker.isTypeSupported(it)) + } + } + + @Test + fun testSupportedTypes_NoWifiDirect() { + doReturn(false).`when`(mPm).hasSystemFeature(FEATURE_WIFI_DIRECT) + val tracker = makeTracker() + assertFalse(tracker.isTypeSupported(TYPE_WIFI_P2P)) + supportedTypes.toSet().minus(TYPE_WIFI_P2P).forEach { + assertTrue(tracker.isTypeSupported(it)) + } + } + + @Test + fun testSupl() { + val tracker = makeTracker() + val mobileNai = mock(NetworkAgentInfo::class.java) + tracker.add(TYPE_MOBILE, mobileNai) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE) + reset(mMockService) + tracker.add(TYPE_MOBILE_SUPL, mobileNai) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL) + reset(mMockService) + tracker.remove(TYPE_MOBILE_SUPL, mobileNai, false /* wasDefault */) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL) + reset(mMockService) + tracker.add(TYPE_MOBILE_SUPL, mobileNai) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, CONNECTED, TYPE_MOBILE_SUPL) + reset(mMockService) + tracker.remove(mobileNai, false) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE_SUPL) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai, DISCONNECTED, TYPE_MOBILE) + } + + @Test + fun testAddNetwork() { + val tracker = makeTracker() + val mobileNai = mock(NetworkAgentInfo::class.java) + val wifiNai = mock(NetworkAgentInfo::class.java) + tracker.add(TYPE_MOBILE, mobileNai) + tracker.add(TYPE_WIFI, wifiNai) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) + // Make sure adding a second NAI does not change the results. + val secondMobileNai = mock(NetworkAgentInfo::class.java) + tracker.add(TYPE_MOBILE, secondMobileNai) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) + // Make sure removing a network that wasn't added for this type is a no-op. + tracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), mobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) + // Remove the top network for mobile and make sure the second one becomes the network + // of record for this type. + tracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */) + assertSame(tracker.getNetworkForType(TYPE_MOBILE), secondMobileNai) + assertSame(tracker.getNetworkForType(TYPE_WIFI), wifiNai) + // Make sure adding a network for an unsupported type does not register it. + tracker.add(UNSUPPORTED_TYPE, mobileNai) + assertNull(tracker.getNetworkForType(UNSUPPORTED_TYPE)) + } + + @Test + fun testBroadcastOnDisconnect() { + val tracker = makeTracker() + val mobileNai1 = mock(NetworkAgentInfo::class.java) + val mobileNai2 = mock(NetworkAgentInfo::class.java) + doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1) + tracker.add(TYPE_MOBILE, mobileNai1) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE) + reset(mMockService) + doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2) + tracker.add(TYPE_MOBILE, mobileNai2) + verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt()) + tracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE) + verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE) + } +} diff --git a/tests/unit/java/com/android/server/NetIdManagerTest.kt b/tests/unit/java/com/android/server/NetIdManagerTest.kt new file mode 100644 index 0000000000..6f5e740d34 --- /dev/null +++ b/tests/unit/java/com/android/server/NetIdManagerTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.server.NetIdManager.MIN_NET_ID +import com.android.testutils.assertThrows +import com.android.testutils.ExceptionUtils.ThrowingRunnable +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetIdManagerTest { + @Test + fun testReserveReleaseNetId() { + val manager = NetIdManager(MIN_NET_ID + 4) + assertEquals(MIN_NET_ID, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 1, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 2, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 3, manager.reserveNetId()) + + manager.releaseNetId(MIN_NET_ID + 1) + manager.releaseNetId(MIN_NET_ID + 3) + // IDs only loop once there is no higher ID available + assertEquals(MIN_NET_ID + 4, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 1, manager.reserveNetId()) + assertEquals(MIN_NET_ID + 3, manager.reserveNetId()) + assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() }) + manager.releaseNetId(MIN_NET_ID + 5) + // Still no ID available: MIN_NET_ID + 5 was not reserved + assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() }) + manager.releaseNetId(MIN_NET_ID + 2) + // Throwing an exception still leaves the manager in a working state + assertEquals(MIN_NET_ID + 2, manager.reserveNetId()) + } +} \ No newline at end of file diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java new file mode 100644 index 0000000000..13516d75a5 --- /dev/null +++ b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java @@ -0,0 +1,315 @@ +/* + * 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 com.android.server; + +import static android.util.DebugUtils.valueToString; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.INetd; +import android.net.INetdUnsolicitedEventListener; +import android.net.LinkAddress; +import android.net.NetworkPolicyManager; +import android.os.BatteryStats; +import android.os.Binder; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.ArrayMap; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.app.IBatteryStats; +import com.android.server.NetworkManagementService.Dependencies; +import com.android.server.net.BaseNetworkObserver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.BiFunction; + +/** + * Tests for {@link NetworkManagementService}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkManagementServiceTest { + private NetworkManagementService mNMService; + @Mock private Context mContext; + @Mock private IBatteryStats.Stub mBatteryStatsService; + @Mock private INetd.Stub mNetdService; + + private static final int TEST_UID = 111; + + @NonNull + @Captor + private ArgumentCaptor mUnsolListenerCaptor; + + private final MockDependencies mDeps = new MockDependencies(); + + private final class MockDependencies extends Dependencies { + @Override + public IBinder getService(String name) { + switch (name) { + case BatteryStats.SERVICE_NAME: + return mBatteryStatsService; + default: + throw new UnsupportedOperationException("Unknown service " + name); + } + } + + @Override + public void registerLocalService(NetworkManagementInternal nmi) { + } + + @Override + public INetd getNetd() { + return mNetdService; + } + + @Override + public int getCallingUid() { + return Process.SYSTEM_UID; + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doNothing().when(mNetdService) + .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture()); + // Start the service and wait until it connects to our socket. + mNMService = NetworkManagementService.create(mContext, mDeps); + } + + @After + public void tearDown() throws Exception { + mNMService.shutdown(); + } + + private static T expectSoon(T mock) { + return verify(mock, timeout(200)); + } + + /** + * Tests that network observers work properly. + */ + @Test + public void testNetworkObservers() throws Exception { + BaseNetworkObserver observer = mock(BaseNetworkObserver.class); + doReturn(new Binder()).when(observer).asBinder(); // Used by registerObserver. + mNMService.registerObserver(observer); + + // Forget everything that happened to the mock so far, so we can explicitly verify + // everything that happens and does not happen to it from now on. + + INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue(); + reset(observer); + // Now call unsolListener methods and ensure that the observer methods are + // called. After every method we expect a callback soon after; to ensure that + // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end. + + /** + * Interface changes. + */ + unsolListener.onInterfaceAdded("rmnet12"); + expectSoon(observer).interfaceAdded("rmnet12"); + + unsolListener.onInterfaceRemoved("eth1"); + expectSoon(observer).interfaceRemoved("eth1"); + + unsolListener.onInterfaceChanged("clat4", true); + expectSoon(observer).interfaceStatusChanged("clat4", true); + + unsolListener.onInterfaceLinkStateChanged("rmnet0", false); + expectSoon(observer).interfaceLinkStateChanged("rmnet0", false); + + /** + * Bandwidth control events. + */ + unsolListener.onQuotaLimitReached("data", "rmnet_usb0"); + expectSoon(observer).limitReached("data", "rmnet_usb0"); + + /** + * Interface class activity. + */ + unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID); + expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID); + + unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID); + expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID); + + unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID); + expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID); + + /** + * IP address changes. + */ + unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253); + expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253)); + + unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253); + expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253)); + + unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0); + expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0)); + + /** + * DNS information broadcasts. + */ + unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"}); + expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600, + new String[]{"2001:db8::1"}); + + unsolListener.onInterfaceDnsServerInfo("wlan0", 14400, + new String[]{"2001:db8::1", "2001:db8::2"}); + expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400, + new String[]{"2001:db8::1", "2001:db8::2"}); + + // We don't check for negative lifetimes, only for parse errors. + unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"}); + expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600, + new String[]{"::1"}); + + // No syntax checking on the addresses. + unsolListener.onInterfaceDnsServerInfo("wlan0", 600, + new String[]{"", "::", "", "foo", "::1"}); + expectSoon(observer).interfaceDnsServerInfo("wlan0", 600, + new String[]{"", "::", "", "foo", "::1"}); + + // Make sure nothing else was called. + verifyNoMoreInteractions(observer); + } + + @Test + public void testFirewallEnabled() { + mNMService.setFirewallEnabled(true); + assertTrue(mNMService.isFirewallEnabled()); + + mNMService.setFirewallEnabled(false); + assertFalse(mNMService.isFirewallEnabled()); + } + + @Test + public void testNetworkRestrictedDefault() { + assertFalse(mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test + public void testMeteredNetworkRestrictions() throws RemoteException { + // Make sure the mocked netd method returns true. + doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean()); + + // Restrict usage of mobile data in background + mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true); + assertTrue("Should be true since mobile data usage is restricted", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setDataSaverModeEnabled(true); + verify(mNetdService).bandwidthEnableDataSaver(true); + + mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false); + assertTrue("Should be true since data saver is on and the uid is not allowlisted", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true); + assertFalse("Should be false since data saver is on and the uid is allowlisted", + mNMService.isNetworkRestricted(TEST_UID)); + + // remove uid from allowlist and turn datasaver off again + mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false); + mNMService.setDataSaverModeEnabled(false); + verify(mNetdService).bandwidthEnableDataSaver(false); + assertFalse("Network should not be restricted when data saver is off", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test + public void testFirewallChains() { + final ArrayMap> expected = new ArrayMap<>(); + // Dozable chain + final ArrayMap isRestrictedForDozable = new ArrayMap<>(); + isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true); + isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false); + isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true); + expected.put(INetd.FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable); + // Powersaver chain + final ArrayMap isRestrictedForPowerSave = new ArrayMap<>(); + isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true); + isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false); + isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true); + expected.put(INetd.FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave); + // Standby chain + final ArrayMap isRestrictedForStandby = new ArrayMap<>(); + isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false); + isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false); + isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true); + expected.put(INetd.FIREWALL_CHAIN_STANDBY, isRestrictedForStandby); + // Restricted mode chain + final ArrayMap isRestrictedForRestrictedMode = new ArrayMap<>(); + isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true); + isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false); + isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true); + expected.put(INetd.FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode); + + final int[] chains = { + INetd.FIREWALL_CHAIN_STANDBY, + INetd.FIREWALL_CHAIN_POWERSAVE, + INetd.FIREWALL_CHAIN_DOZABLE, + INetd.FIREWALL_CHAIN_RESTRICTED + }; + final int[] states = { + INetd.FIREWALL_RULE_ALLOW, + INetd.FIREWALL_RULE_DENY, + NetworkPolicyManager.FIREWALL_RULE_DEFAULT + }; + BiFunction errorMsg = (chain, state) -> { + return String.format("Unexpected value for chain: %s and state: %s", + valueToString(INetd.class, "FIREWALL_CHAIN_", chain), + valueToString(INetd.class, "FIREWALL_RULE_", state)); + }; + for (int chain : chains) { + final ArrayMap expectedValues = expected.get(chain); + mNMService.setFirewallChainEnabled(chain, true); + for (int state : states) { + mNMService.setFirewallUidRule(chain, TEST_UID, state); + assertEquals(errorMsg.apply(chain, state), + expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID)); + } + mNMService.setFirewallChainEnabled(chain, false); + } + } +} diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java new file mode 100644 index 0000000000..a90fa6882c --- /dev/null +++ b/tests/unit/java/com/android/server/NsdServiceTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.nsd.NsdManager; +import android.net.nsd.NsdServiceInfo; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.NsdService.DaemonConnection; +import com.android.server.NsdService.DaemonConnectionSupplier; +import com.android.server.NsdService.NativeCallbackReceiver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +// TODOs: +// - test client can send requests and receive replies +// - test NSD_ON ENABLE/DISABLED listening +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NsdServiceTest { + + static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD; + + long mTimeoutMs = 100; // non-final so that tests can adjust the value. + + @Mock Context mContext; + @Mock ContentResolver mResolver; + @Mock NsdService.NsdSettings mSettings; + @Mock DaemonConnection mDaemon; + NativeCallbackReceiver mDaemonCallback; + HandlerThread mThread; + TestHandler mHandler; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mThread = new HandlerThread("mock-service-handler"); + mThread.start(); + mHandler = new TestHandler(mThread.getLooper()); + when(mContext.getContentResolver()).thenReturn(mResolver); + } + + @After + public void tearDown() throws Exception { + if (mThread != null) { + mThread.quit(); + mThread = null; + } + } + + @Test + public void testClientsCanConnectAndDisconnect() { + when(mSettings.isEnabled()).thenReturn(true); + + NsdService service = makeService(); + + NsdManager client1 = connectClient(service); + verify(mDaemon, timeout(100).times(1)).start(); + + NsdManager client2 = connectClient(service); + + client1.disconnect(); + client2.disconnect(); + + verify(mDaemon, timeout(mTimeoutMs).times(1)).stop(); + + client1.disconnect(); + client2.disconnect(); + } + + @Test + public void testClientRequestsAreGCedAtDisconnection() { + when(mSettings.isEnabled()).thenReturn(true); + when(mDaemon.execute(any())).thenReturn(true); + + NsdService service = makeService(); + NsdManager client = connectClient(service); + + verify(mDaemon, timeout(100).times(1)).start(); + + NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type"); + request.setPort(2201); + + // Client registration request + NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class); + client.registerService(request, PROTOCOL, listener1); + verifyDaemonCommand("register 2 a_name a_type 2201"); + + // Client discovery request + NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class); + client.discoverServices("a_type", PROTOCOL, listener2); + verifyDaemonCommand("discover 3 a_type"); + + // Client resolve request + NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class); + client.resolveService(request, listener3); + verifyDaemonCommand("resolve 4 a_name a_type local."); + + // Client disconnects + client.disconnect(); + verify(mDaemon, timeout(mTimeoutMs).times(1)).stop(); + + // checks that request are cleaned + verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4"); + + client.disconnect(); + } + + NsdService makeService() { + DaemonConnectionSupplier supplier = (callback) -> { + mDaemonCallback = callback; + return mDaemon; + }; + NsdService service = new NsdService(mContext, mSettings, mHandler, supplier); + verify(mDaemon, never()).execute(any(String.class)); + return service; + } + + NsdManager connectClient(NsdService service) { + return new NsdManager(mContext, service); + } + + void verifyDaemonCommands(String... wants) { + verifyDaemonCommand(String.join(" ", wants), wants.length); + } + + void verifyDaemonCommand(String want) { + verifyDaemonCommand(want, 1); + } + + void verifyDaemonCommand(String want, int n) { + ArgumentCaptor argumentsCaptor = ArgumentCaptor.forClass(Object.class); + verify(mDaemon, timeout(mTimeoutMs).times(n)).execute(argumentsCaptor.capture()); + String got = ""; + for (Object o : argumentsCaptor.getAllValues()) { + got += o + " "; + } + assertEquals(want, got.trim()); + // rearm deamon for next command verification + reset(mDaemon); + when(mDaemon.execute(any())).thenReturn(true); + } + + public static class TestHandler extends Handler { + public Message lastMessage; + + TestHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + lastMessage = obtainMessage(); + lastMessage.copyFrom(msg); + } + } +} diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java new file mode 100644 index 0000000000..0ffeec98cf --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER; +import static android.net.NetworkCapabilities.MAX_TRANSPORT; +import static android.net.NetworkCapabilities.MIN_TRANSPORT; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; +import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; + +import static com.android.testutils.MiscAsserts.assertContainsExactly; +import static com.android.testutils.MiscAsserts.assertContainsStringsExactly; +import static com.android.testutils.MiscAsserts.assertFieldCountEquals; + +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.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.ConnectivitySettingsManager; +import android.net.IDnsResolver; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.ResolverOptionsParcel; +import android.net.ResolverParamsParcel; +import android.net.RouteInfo; +import android.net.shared.PrivateDnsConfig; +import android.provider.Settings; +import android.test.mock.MockContentResolver; +import android.util.SparseArray; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.MessageUtils; +import com.android.internal.util.test.FakeSettingsProvider; + +import libcore.net.InetAddressUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.net.InetAddress; +import java.util.Arrays; + +/** + * Tests for {@link DnsManager}. + * + * Build, install and run with: + * runtest frameworks-net -c com.android.server.connectivity.DnsManagerTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DnsManagerTest { + static final String TEST_IFACENAME = "test_wlan0"; + static final int TEST_NETID = 100; + static final int TEST_NETID_ALTERNATE = 101; + static final int TEST_NETID_UNTRACKED = 102; + static final int TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800; + static final int TEST_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25; + static final int TEST_DEFAULT_MIN_SAMPLES = 8; + static final int TEST_DEFAULT_MAX_SAMPLES = 64; + static final int[] TEST_TRANSPORT_TYPES = {TRANSPORT_WIFI, TRANSPORT_VPN}; + + DnsManager mDnsManager; + MockContentResolver mContentResolver; + + @Mock Context mCtx; + @Mock IDnsResolver mMockDnsResolver; + + private void assertResolverOptionsEquals( + @NonNull ResolverOptionsParcel actual, + @NonNull ResolverOptionsParcel expected) { + assertEquals(actual.hosts, expected.hosts); + assertEquals(actual.tcMode, expected.tcMode); + assertEquals(actual.enforceDnsUid, expected.enforceDnsUid); + assertFieldCountEquals(3, ResolverOptionsParcel.class); + } + + private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual, + @NonNull ResolverParamsParcel expected) { + assertEquals(actual.netId, expected.netId); + assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds); + assertEquals(actual.successThreshold, expected.successThreshold); + assertEquals(actual.minSamples, expected.minSamples); + assertEquals(actual.maxSamples, expected.maxSamples); + assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec); + assertEquals(actual.retryCount, expected.retryCount); + assertContainsStringsExactly(actual.servers, expected.servers); + assertContainsStringsExactly(actual.domains, expected.domains); + assertEquals(actual.tlsName, expected.tlsName); + assertContainsStringsExactly(actual.tlsServers, expected.tlsServers); + assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints); + assertEquals(actual.caCertificate, expected.caCertificate); + assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs); + assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions); + assertContainsExactly(actual.transportTypes, expected.transportTypes); + assertFieldCountEquals(16, ResolverParamsParcel.class); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, + new FakeSettingsProvider()); + when(mCtx.getContentResolver()).thenReturn(mContentResolver); + mDnsManager = new DnsManager(mCtx, mMockDnsResolver); + + // Clear the private DNS settings + Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, ""); + Settings.Global.putString(mContentResolver, PRIVATE_DNS_MODE, ""); + Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, ""); + } + + @Test + public void testTrackedValidationUpdates() throws Exception { + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + mDnsManager.updatePrivateDns(new Network(TEST_NETID_ALTERNATE), + mDnsManager.getPrivateDnsConfig()); + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(TEST_IFACENAME); + lp.addDnsServer(InetAddress.getByName("3.3.3.3")); + lp.addDnsServer(InetAddress.getByName("4.4.4.4")); + + // Send a validation event that is tracked on the alternate netId + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.flushVmDnsCache(); + mDnsManager.updateTransportsForNetwork(TEST_NETID_ALTERNATE, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID_ALTERNATE, lp); + mDnsManager.flushVmDnsCache(); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_ALTERNATE, + InetAddress.parseNumericAddress("4.4.4.4"), "", + VALIDATION_RESULT_SUCCESS)); + LinkProperties fixedLp = new LinkProperties(lp); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); + assertFalse(fixedLp.isPrivateDnsActive()); + assertNull(fixedLp.getPrivateDnsServerName()); + fixedLp = new LinkProperties(lp); + mDnsManager.updatePrivateDnsStatus(TEST_NETID_ALTERNATE, fixedLp); + assertTrue(fixedLp.isPrivateDnsActive()); + assertNull(fixedLp.getPrivateDnsServerName()); + assertEquals(Arrays.asList(InetAddress.getByName("4.4.4.4")), + fixedLp.getValidatedPrivateDnsServers()); + + // Set up addresses for strict mode and switch to it. + lp.addLinkAddress(new LinkAddress("192.0.2.4/24")); + lp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"), + TEST_IFACENAME)); + lp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); + lp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"), + TEST_IFACENAME)); + + ConnectivitySettingsManager.setPrivateDnsMode(mCtx, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); + ConnectivitySettingsManager.setPrivateDnsHostname(mCtx, "strictmode.com"); + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + new PrivateDnsConfig("strictmode.com", new InetAddress[] { + InetAddress.parseNumericAddress("6.6.6.6"), + InetAddress.parseNumericAddress("2001:db8:66:66::1") + })); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.flushVmDnsCache(); + fixedLp = new LinkProperties(lp); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); + assertTrue(fixedLp.isPrivateDnsActive()); + assertEquals("strictmode.com", fixedLp.getPrivateDnsServerName()); + // No validation events yet. + assertEquals(Arrays.asList(new InetAddress[0]), fixedLp.getValidatedPrivateDnsServers()); + // Validate one. + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("6.6.6.6"), "strictmode.com", + VALIDATION_RESULT_SUCCESS)); + fixedLp = new LinkProperties(lp); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); + assertEquals(Arrays.asList(InetAddress.parseNumericAddress("6.6.6.6")), + fixedLp.getValidatedPrivateDnsServers()); + // Validate the 2nd one. + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("2001:db8:66:66::1"), "strictmode.com", + VALIDATION_RESULT_SUCCESS)); + fixedLp = new LinkProperties(lp); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, fixedLp); + assertEquals(Arrays.asList( + InetAddress.parseNumericAddress("2001:db8:66:66::1"), + InetAddress.parseNumericAddress("6.6.6.6")), + fixedLp.getValidatedPrivateDnsServers()); + } + + @Test + public void testIgnoreUntrackedValidationUpdates() throws Exception { + // The PrivateDnsConfig map is empty, so no validation events will + // be tracked. + LinkProperties lp = new LinkProperties(); + lp.addDnsServer(InetAddress.getByName("3.3.3.3")); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.flushVmDnsCache(); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Validation event has untracked netId + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.flushVmDnsCache(); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID_UNTRACKED, + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Validation event has untracked ipAddress + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("4.4.4.4"), "", + VALIDATION_RESULT_SUCCESS)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Validation event has untracked hostname + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "hostname", + VALIDATION_RESULT_SUCCESS)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Validation event failed + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_FAILURE)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Network removed + mDnsManager.removeNetwork(new Network(TEST_NETID)); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "", VALIDATION_RESULT_SUCCESS)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + + // Turn private DNS mode off + ConnectivitySettingsManager.setPrivateDnsMode(mCtx, PRIVATE_DNS_MODE_OFF); + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.flushVmDnsCache(); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, + InetAddress.parseNumericAddress("3.3.3.3"), "", + VALIDATION_RESULT_SUCCESS)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + assertFalse(lp.isPrivateDnsActive()); + assertNull(lp.getPrivateDnsServerName()); + } + + @Test + public void testOverrideDefaultMode() throws Exception { + // Hard-coded default is opportunistic mode. + final PrivateDnsConfig cfgAuto = DnsManager.getPrivateDnsConfig(mCtx); + assertTrue(cfgAuto.useTls); + assertEquals("", cfgAuto.hostname); + assertEquals(new InetAddress[0], cfgAuto.ips); + + // Pretend a gservices push sets the default to "off". + ConnectivitySettingsManager.setPrivateDnsDefaultMode(mCtx, PRIVATE_DNS_MODE_OFF); + final PrivateDnsConfig cfgOff = DnsManager.getPrivateDnsConfig(mCtx); + assertFalse(cfgOff.useTls); + assertEquals("", cfgOff.hostname); + assertEquals(new InetAddress[0], cfgOff.ips); + + // Strict mode still works. + ConnectivitySettingsManager.setPrivateDnsMode(mCtx, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); + ConnectivitySettingsManager.setPrivateDnsHostname(mCtx, "strictmode.com"); + final PrivateDnsConfig cfgStrict = DnsManager.getPrivateDnsConfig(mCtx); + assertTrue(cfgStrict.useTls); + assertEquals("strictmode.com", cfgStrict.hostname); + assertEquals(new InetAddress[0], cfgStrict.ips); + } + + @Test + public void testSendDnsConfiguration() throws Exception { + reset(mMockDnsResolver); + mDnsManager.updatePrivateDns(new Network(TEST_NETID), + mDnsManager.getPrivateDnsConfig()); + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(TEST_IFACENAME); + lp.addDnsServer(InetAddress.getByName("3.3.3.3")); + lp.addDnsServer(InetAddress.getByName("4.4.4.4")); + mDnsManager.updateTransportsForNetwork(TEST_NETID, TEST_TRANSPORT_TYPES); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.flushVmDnsCache(); + + final ArgumentCaptor resolverParamsParcelCaptor = + ArgumentCaptor.forClass(ResolverParamsParcel.class); + verify(mMockDnsResolver, times(1)).setResolverConfiguration( + resolverParamsParcelCaptor.capture()); + final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue(); + final ResolverParamsParcel expectedParams = new ResolverParamsParcel(); + expectedParams.netId = TEST_NETID; + expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS; + expectedParams.successThreshold = TEST_DEFAULT_SUCCESS_THRESHOLD_PERCENT; + expectedParams.minSamples = TEST_DEFAULT_MIN_SAMPLES; + expectedParams.maxSamples = TEST_DEFAULT_MAX_SAMPLES; + expectedParams.servers = new String[]{"3.3.3.3", "4.4.4.4"}; + expectedParams.domains = new String[]{}; + expectedParams.tlsName = ""; + expectedParams.tlsServers = new String[]{"3.3.3.3", "4.4.4.4"}; + expectedParams.transportTypes = TEST_TRANSPORT_TYPES; + expectedParams.resolverOptions = new ResolverOptionsParcel(); + assertResolverParamsEquals(actualParams, expectedParams); + } + + @Test + public void testTransportTypesEqual() throws Exception { + SparseArray ncTransTypes = MessageUtils.findMessageNames( + new Class[] { NetworkCapabilities.class }, new String[]{ "TRANSPORT_" }); + SparseArray dnsTransTypes = MessageUtils.findMessageNames( + new Class[] { IDnsResolver.class }, new String[]{ "TRANSPORT_" }); + assertEquals(0, MIN_TRANSPORT); + assertEquals(MAX_TRANSPORT + 1, ncTransTypes.size()); + // TRANSPORT_UNKNOWN in IDnsResolver is defined to -1 and only for resolver. + assertEquals("TRANSPORT_UNKNOWN", dnsTransTypes.get(-1)); + assertEquals(ncTransTypes.size(), dnsTransTypes.size() - 1); + for (int i = MIN_TRANSPORT; i < MAX_TRANSPORT; i++) { + String name = ncTransTypes.get(i, null); + assertNotNull("Could not find NetworkCapabilies.TRANSPORT_* constant equal to " + + i, name); + assertEquals(name, dnsTransTypes.get(i)); + } + } + + @Test + public void testGetPrivateDnsConfigForNetwork() throws Exception { + final Network network = new Network(TEST_NETID); + final InetAddress dnsAddr = InetAddressUtils.parseNumericAddress("3.3.3.3"); + final InetAddress[] tlsAddrs = new InetAddress[]{ + InetAddressUtils.parseNumericAddress("6.6.6.6"), + InetAddressUtils.parseNumericAddress("2001:db8:66:66::1") + }; + final String tlsName = "strictmode.com"; + LinkProperties lp = new LinkProperties(); + lp.addDnsServer(dnsAddr); + + // The PrivateDnsConfig map is empty, so the default PRIVATE_DNS_OFF is returned. + PrivateDnsConfig privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); + assertFalse(privateDnsCfg.useTls); + assertEquals("", privateDnsCfg.hostname); + assertEquals(new InetAddress[0], privateDnsCfg.ips); + + // An entry with default PrivateDnsConfig is added to the PrivateDnsConfig map. + mDnsManager.updatePrivateDns(network, mDnsManager.getPrivateDnsConfig()); + mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp); + mDnsManager.updatePrivateDnsValidation( + new DnsManager.PrivateDnsValidationUpdate(TEST_NETID, dnsAddr, "", + VALIDATION_RESULT_SUCCESS)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); + assertTrue(privateDnsCfg.useTls); + assertEquals("", privateDnsCfg.hostname); + assertEquals(new InetAddress[0], privateDnsCfg.ips); + + // The original entry is overwritten by a new PrivateDnsConfig. + mDnsManager.updatePrivateDns(network, new PrivateDnsConfig(tlsName, tlsAddrs)); + mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp); + privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); + assertTrue(privateDnsCfg.useTls); + assertEquals(tlsName, privateDnsCfg.hostname); + assertEquals(tlsAddrs, privateDnsCfg.ips); + + // The network is removed, so the PrivateDnsConfig map becomes empty again. + mDnsManager.removeNetwork(network); + privateDnsCfg = mDnsManager.getPrivateDnsConfig(network); + assertFalse(privateDnsCfg.useTls); + assertEquals("", privateDnsCfg.hostname); + assertEquals(new InetAddress[0], privateDnsCfg.ips); + } +} diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt new file mode 100644 index 0000000000..eb3b4df1a2 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt @@ -0,0 +1,134 @@ +/* + * 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.server.connectivity + +import android.net.NetworkAgentConfig +import android.net.NetworkCapabilities +import android.text.TextUtils +import android.util.ArraySet +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY +import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED +import com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED +import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED +import com.android.server.connectivity.FullScore.POLICY_IS_VPN +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.collections.minOfOrNull +import kotlin.collections.maxOfOrNull +import kotlin.reflect.full.staticProperties +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +@SmallTest +class FullScoreTest { + // Convenience methods + fun FullScore.withPolicies( + validated: Boolean = false, + vpn: Boolean = false, + onceChosen: Boolean = false, + acceptUnvalidated: Boolean = false + ): FullScore { + val nac = NetworkAgentConfig.Builder().apply { + setUnvalidatedConnectivityAcceptable(acceptUnvalidated) + setExplicitlySelected(onceChosen) + }.build() + val nc = NetworkCapabilities.Builder().apply { + if (vpn) addTransportType(NetworkCapabilities.TRANSPORT_VPN) + if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + }.build() + return mixInScore(nc, nac) + } + + @Test + fun testGetLegacyInt() { + val ns = FullScore(50, 0L /* policy */) + assertEquals(10, ns.legacyInt) // -40 penalty for not being validated + assertEquals(50, ns.legacyIntAsValidated) + + val vpnNs = FullScore(101, 0L /* policy */).withPolicies(vpn = true) + assertEquals(101, vpnNs.legacyInt) // VPNs are not subject to unvalidation penalty + assertEquals(101, vpnNs.legacyIntAsValidated) + assertEquals(101, vpnNs.withPolicies(validated = true).legacyInt) + assertEquals(101, vpnNs.withPolicies(validated = true).legacyIntAsValidated) + + val validatedNs = ns.withPolicies(validated = true) + assertEquals(50, validatedNs.legacyInt) // No penalty, this is validated + assertEquals(50, validatedNs.legacyIntAsValidated) + + val chosenNs = ns.withPolicies(onceChosen = true) + assertEquals(10, chosenNs.legacyInt) + assertEquals(100, chosenNs.legacyIntAsValidated) + assertEquals(10, chosenNs.withPolicies(acceptUnvalidated = true).legacyInt) + assertEquals(50, chosenNs.withPolicies(acceptUnvalidated = true).legacyIntAsValidated) + } + + @Test + fun testToString() { + val string = FullScore(10, 0L /* policy */) + .withPolicies(vpn = true, acceptUnvalidated = true).toString() + assertTrue(string.contains("Score(10"), string) + assertTrue(string.contains("ACCEPT_UNVALIDATED"), string) + assertTrue(string.contains("IS_VPN"), string) + assertFalse(string.contains("IS_VALIDATED"), string) + val foundNames = ArraySet() + getAllPolicies().forEach { + val name = FullScore.policyNameOf(it.get() as Int) + assertFalse(TextUtils.isEmpty(name)) + assertFalse(foundNames.contains(name)) + foundNames.add(name) + } + assertFailsWith { + FullScore.policyNameOf(MAX_CS_MANAGED_POLICY + 1) + } + } + + fun getAllPolicies() = Regex("POLICY_.*").let { nameRegex -> + FullScore::class.staticProperties.filter { it.name.matches(nameRegex) } + } + + @Test + fun testHasPolicy() { + val ns = FullScore(50, 0L /* policy */) + assertFalse(ns.hasPolicy(POLICY_IS_VALIDATED)) + assertFalse(ns.hasPolicy(POLICY_IS_VPN)) + assertFalse(ns.hasPolicy(POLICY_EVER_USER_SELECTED)) + assertFalse(ns.hasPolicy(POLICY_ACCEPT_UNVALIDATED)) + assertTrue(ns.withPolicies(validated = true).hasPolicy(POLICY_IS_VALIDATED)) + assertTrue(ns.withPolicies(vpn = true).hasPolicy(POLICY_IS_VPN)) + assertTrue(ns.withPolicies(onceChosen = true).hasPolicy(POLICY_EVER_USER_SELECTED)) + assertTrue(ns.withPolicies(acceptUnvalidated = true).hasPolicy(POLICY_ACCEPT_UNVALIDATED)) + } + + @Test + fun testMinMaxPolicyConstants() { + val policies = getAllPolicies() + + policies.forEach { policy -> + assertTrue(policy.get() as Int >= FullScore.MIN_CS_MANAGED_POLICY) + assertTrue(policy.get() as Int <= FullScore.MAX_CS_MANAGED_POLICY) + } + assertEquals(FullScore.MIN_CS_MANAGED_POLICY, + policies.minOfOrNull { it.get() as Int }) + assertEquals(FullScore.MAX_CS_MANAGED_POLICY, + policies.maxOfOrNull { it.get() as Int }) + } +} diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java new file mode 100644 index 0000000000..70495cced5 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static com.android.server.connectivity.MetricsTestUtil.aLong; +import static com.android.server.connectivity.MetricsTestUtil.aString; +import static com.android.server.connectivity.MetricsTestUtil.aType; +import static com.android.server.connectivity.MetricsTestUtil.anInt; +import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent; +import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.BLUETOOTH; +import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.CELLULAR; +import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; +import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE; +import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.net.ConnectivityMetricsEvent; +import android.net.metrics.ApfProgramEvent; +import android.net.metrics.ApfStats; +import android.net.metrics.DefaultNetworkEvent; +import android.net.metrics.DhcpClientEvent; +import android.net.metrics.DhcpErrorEvent; +import android.net.metrics.IpManagerEvent; +import android.net.metrics.IpReachabilityEvent; +import android.net.metrics.NetworkEvent; +import android.net.metrics.RaEvent; +import android.net.metrics.ValidationProbeEvent; +import android.net.metrics.WakeupStats; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; + +// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpConnectivityEventBuilderTest { + + @Test + public void testLinkLayerInferrence() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(IpReachabilityEvent.class), + anInt(IpReachabilityEvent.NUD_FAILED)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "version: 2\n"); + verifySerialization(want, ev); + + ev.netId = 123; + ev.transports = 3; // transports have priority for inferrence of link layer + ev.ifname = "wlan0"; + want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + String.format(" link_layer: %d", MULTIPLE), + " network_id: 123", + " time_ms: 1", + " transports: 3", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "version: 2\n"); + verifySerialization(want, ev); + + ev.transports = 1; + ev.ifname = null; + want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + String.format(" link_layer: %d", CELLULAR), + " network_id: 123", + " time_ms: 1", + " transports: 1", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "version: 2\n"); + verifySerialization(want, ev); + + ev.transports = 0; + ev.ifname = "not_inferred"; + want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"not_inferred\"", + " link_layer: 0", + " network_id: 123", + " time_ms: 1", + " transports: 0", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "version: 2\n"); + verifySerialization(want, ev); + + ev.ifname = "bt-pan"; + want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + String.format(" link_layer: %d", BLUETOOTH), + " network_id: 123", + " time_ms: 1", + " transports: 0", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "version: 2\n"); + verifySerialization(want, ev); + + ev.ifname = "rmnet_ipa0"; + want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + String.format(" link_layer: %d", CELLULAR), + " network_id: 123", + " time_ms: 1", + " transports: 0", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "version: 2\n"); + verifySerialization(want, ev); + + ev.ifname = "wlan0"; + want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + String.format(" link_layer: %d", WIFI), + " network_id: 123", + " time_ms: 1", + " transports: 0", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "version: 2\n"); + verifySerialization(want, ev); + } + + @Test + public void testDefaultNetworkEventSerialization() { + DefaultNetworkEvent ev = new DefaultNetworkEvent(1001); + ev.netId = 102; + ev.transports = 2; + ev.previousTransports = 4; + ev.ipv4 = true; + ev.initialScore = 20; + ev.finalScore = 60; + ev.durationMs = 54; + ev.validatedMs = 27; + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 102", + " time_ms: 0", + " transports: 2", + " default_network_event <", + " default_network_duration_ms: 54", + " final_score: 60", + " initial_score: 20", + " ip_support: 1", + " no_default_network_duration_ms: 0", + " previous_default_network_link_layer: 1", + " previous_network_ip_support: 0", + " validation_duration_ms: 27", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, IpConnectivityEventBuilder.toProto(ev)); + } + + @Test + public void testDhcpClientEventSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(DhcpClientEvent.class), + aString("SomeState"), + anInt(192)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " dhcp_event <", + " duration_ms: 192", + " if_name: \"\"", + " state_transition: \"SomeState\"", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testDhcpErrorEventSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(DhcpErrorEvent.class), + anInt(DhcpErrorEvent.L4_NOT_UDP)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " dhcp_event <", + " duration_ms: 0", + " if_name: \"\"", + " error_code: 50397184", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testIpManagerEventSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(IpManagerEvent.class), + anInt(IpManagerEvent.PROVISIONING_OK), + aLong(5678)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " ip_provisioning_event <", + " event_type: 1", + " if_name: \"\"", + " latency_ms: 5678", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testIpReachabilityEventSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(IpReachabilityEvent.class), + anInt(IpReachabilityEvent.NUD_FAILED)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testNetworkEventSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(NetworkEvent.class), + anInt(5), + aLong(20410)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " network_event <", + " event_type: 5", + " latency_ms: 20410", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testValidationProbeEventSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(ValidationProbeEvent.class), + aLong(40730), + anInt(ValidationProbeEvent.PROBE_HTTP), + anInt(204)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " validation_probe_event <", + " latency_ms: 40730", + " probe_result: 204", + " probe_type: 1", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testApfProgramEventSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(ApfProgramEvent.class), + aLong(200), + aLong(18), + anInt(7), + anInt(9), + anInt(2048), + anInt(3)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " apf_program_event <", + " current_ras: 9", + " drop_multicast: true", + " effective_lifetime: 18", + " filtered_ras: 7", + " has_ipv4_addr: true", + " lifetime: 200", + " program_length: 2048", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testApfStatsSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(ApfStats.class), + aLong(45000), + anInt(10), + anInt(2), + anInt(2), + anInt(1), + anInt(2), + anInt(4), + anInt(7), + anInt(3), + anInt(2048)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " apf_statistics <", + " dropped_ras: 2", + " duration_ms: 45000", + " matching_ras: 2", + " max_program_size: 2048", + " parse_errors: 2", + " program_updates: 4", + " program_updates_all: 7", + " program_updates_allowing_multicast: 3", + " received_ras: 10", + " total_packet_dropped: 0", + " total_packet_processed: 0", + " zero_lifetime_ras: 1", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testRaEventSerialization() { + ConnectivityMetricsEvent ev = describeIpEvent( + aType(RaEvent.class), + aLong(2000), + aLong(400), + aLong(300), + aLong(-1), + aLong(1000), + aLong(-1)); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 0", + " network_id: 0", + " time_ms: 1", + " transports: 0", + " ra_event <", + " dnssl_lifetime: -1", + " prefix_preferred_lifetime: 300", + " prefix_valid_lifetime: 400", + " rdnss_lifetime: 1000", + " route_info_lifetime: -1", + " router_lifetime: 2000", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, ev); + } + + @Test + public void testWakeupStatsSerialization() { + WakeupStats stats = new WakeupStats("wlan0"); + stats.totalWakeups = 14; + stats.applicationWakeups = 5; + stats.nonApplicationWakeups = 1; + stats.rootWakeups = 2; + stats.systemWakeups = 3; + stats.noUidWakeups = 3; + stats.l2UnicastCount = 5; + stats.l2MulticastCount = 1; + stats.l2BroadcastCount = 2; + stats.ethertypes.put(0x800, 3); + stats.ethertypes.put(0x86dd, 3); + stats.ipNextHeaders.put(6, 5); + + + IpConnectivityEvent got = IpConnectivityEventBuilder.toProto(stats); + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 5", + " duration_sec: 0", + " ethertype_counts <", + " key: 2048", + " value: 3", + " >", + " ethertype_counts <", + " key: 34525", + " value: 3", + " >", + " ip_next_header_counts <", + " key: 6", + " value: 5", + " >", + " l2_broadcast_count: 2", + " l2_multicast_count: 1", + " l2_unicast_count: 5", + " no_uid_wakeups: 3", + " non_application_wakeups: 1", + " root_wakeups: 2", + " system_wakeups: 3", + " total_wakeups: 14", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, got); + } + + static void verifySerialization(String want, ConnectivityMetricsEvent... input) { + List protoInput = + IpConnectivityEventBuilder.toProto(Arrays.asList(input)); + verifySerialization(want, protoInput.toArray(new IpConnectivityEvent[0])); + } + + static void verifySerialization(String want, IpConnectivityEvent... input) { + try { + byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input)); + IpConnectivityLog log = IpConnectivityLog.parseFrom(got); + assertEquals(want, log.toString()); + } catch (Exception e) { + fail(e.toString()); + } + } +} diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java new file mode 100644 index 0000000000..8b072c49de --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -0,0 +1,645 @@ +/* + * Copyright (C) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO; +import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.ConnectivityMetricsEvent; +import android.net.IIpConnectivityMetrics; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.RouteInfo; +import android.net.metrics.ApfProgramEvent; +import android.net.metrics.ApfStats; +import android.net.metrics.DhcpClientEvent; +import android.net.metrics.IpConnectivityLog; +import android.net.metrics.IpManagerEvent; +import android.net.metrics.IpReachabilityEvent; +import android.net.metrics.RaEvent; +import android.net.metrics.ValidationProbeEvent; +import android.os.Parcelable; +import android.system.OsConstants; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Base64; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.BitUtils; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.PrintWriter; +import java.io.StringWriter; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpConnectivityMetricsTest { + static final IpReachabilityEvent FAKE_EV = + new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED); + + private static final String EXAMPLE_IPV4 = "192.0.2.1"; + private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1"; + + private static final byte[] MAC_ADDR = + {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b}; + + @Mock Context mCtx; + @Mock IIpConnectivityMetrics mMockService; + @Mock ConnectivityManager mCm; + + IpConnectivityMetrics mService; + NetdEventListenerService mNetdListener; + private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .build(); + private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .build(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000); + mNetdListener = new NetdEventListenerService(mCm); + mService.mNetdListener = mNetdListener; + } + + @Test + public void testBufferFlushing() { + String output1 = getdump("flush"); + assertEquals("", output1); + + new IpConnectivityLog(mService.impl).log(1, FAKE_EV); + String output2 = getdump("flush"); + assertFalse("".equals(output2)); + + String output3 = getdump("flush"); + assertEquals("", output3); + } + + @Test + public void testRateLimiting() { + final IpConnectivityLog logger = new IpConnectivityLog(mService.impl); + final ApfProgramEvent ev = new ApfProgramEvent.Builder().build(); + final long fakeTimestamp = 1; + + int attempt = 100; // More than burst quota, but less than buffer size. + for (int i = 0; i < attempt; i++) { + logger.log(ev); + } + + String output1 = getdump("flush"); + assertFalse("".equals(output1)); + + for (int i = 0; i < attempt; i++) { + assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev)); + } + + String output2 = getdump("flush"); + assertEquals("", output2); + } + + private void logDefaultNetworkEvent(long timeMs, NetworkAgentInfo nai, + NetworkAgentInfo oldNai) { + final Network network = (nai != null) ? nai.network() : null; + final int score = (nai != null) ? nai.getCurrentScore() : 0; + final boolean validated = (nai != null) ? nai.lastValidated : false; + final LinkProperties lp = (nai != null) ? nai.linkProperties : null; + final NetworkCapabilities nc = (nai != null) ? nai.networkCapabilities : null; + + final Network prevNetwork = (oldNai != null) ? oldNai.network() : null; + final int prevScore = (oldNai != null) ? oldNai.getCurrentScore() : 0; + final LinkProperties prevLp = (oldNai != null) ? oldNai.linkProperties : null; + final NetworkCapabilities prevNc = (oldNai != null) ? oldNai.networkCapabilities : null; + + mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, network, score, validated, + lp, nc, prevNetwork, prevScore, prevLp, prevNc); + } + @Test + public void testDefaultNetworkEvents() throws Exception { + final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); + final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + + NetworkAgentInfo[][] defaultNetworks = { + // nothing -> cell + {null, makeNai(100, 10, false, true, cell)}, + // cell -> wifi + {makeNai(100, 50, true, true, cell), makeNai(101, 20, true, false, wifi)}, + // wifi -> nothing + {makeNai(101, 60, true, false, wifi), null}, + // nothing -> cell + {null, makeNai(102, 10, true, true, cell)}, + // cell -> wifi + {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)}, + }; + + long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs; + long durationMs = 1001; + for (NetworkAgentInfo[] pair : defaultNetworks) { + timeMs += durationMs; + durationMs += durationMs; + logDefaultNetworkEvent(timeMs, pair[1], pair[0]); + } + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 5", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 1001", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " no_default_network_duration_ms: 0", + " previous_default_network_link_layer: 0", + " previous_network_ip_support: 0", + " validation_duration_ms: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 100", + " time_ms: 0", + " transports: 1", + " default_network_event <", + " default_network_duration_ms: 2002", + " final_score: 50", + " initial_score: 10", + " ip_support: 3", + " no_default_network_duration_ms: 0", + " previous_default_network_link_layer: 0", + " previous_network_ip_support: 0", + " validation_duration_ms: 2002", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 101", + " time_ms: 0", + " transports: 2", + " default_network_event <", + " default_network_duration_ms: 4004", + " final_score: 60", + " initial_score: 20", + " ip_support: 1", + " no_default_network_duration_ms: 0", + " previous_default_network_link_layer: 2", + " previous_network_ip_support: 0", + " validation_duration_ms: 4004", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 5", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 8008", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " no_default_network_duration_ms: 0", + " previous_default_network_link_layer: 4", + " previous_network_ip_support: 0", + " validation_duration_ms: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 102", + " time_ms: 0", + " transports: 1", + " default_network_event <", + " default_network_duration_ms: 16016", + " final_score: 50", + " initial_score: 10", + " ip_support: 3", + " no_default_network_duration_ms: 0", + " previous_default_network_link_layer: 4", + " previous_network_ip_support: 0", + " validation_duration_ms: 16016", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, getdump("flush")); + } + + @Test + public void testEndToEndLogging() throws Exception { + // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. + IpConnectivityLog logger = new IpConnectivityLog(mService.impl); + + ApfStats apfStats = new ApfStats.Builder() + .setDurationMs(45000) + .setReceivedRas(10) + .setMatchingRas(2) + .setDroppedRas(2) + .setParseErrors(2) + .setZeroLifetimeRas(1) + .setProgramUpdates(4) + .setProgramUpdatesAll(7) + .setProgramUpdatesAllowingMulticast(3) + .setMaxProgramSize(2048) + .build(); + + final ValidationProbeEvent validationEv = new ValidationProbeEvent.Builder() + .setDurationMs(40730) + .setProbeType(ValidationProbeEvent.PROBE_HTTP, true) + .setReturnCode(204) + .build(); + + final DhcpClientEvent event = new DhcpClientEvent.Builder() + .setMsg("SomeState") + .setDurationMs(192) + .build(); + Parcelable[] events = { + new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED), event, + new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678), + validationEv, + apfStats, + new RaEvent(2000, 400, 300, -1, 1000, -1) + }; + + for (int i = 0; i < events.length; i++) { + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.timestamp = 100 * (i + 1); + ev.ifname = "wlan0"; + ev.data = events[i]; + logger.log(ev); + } + + // netId, errno, latency, destination + connectEvent(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4); + connectEvent(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6); + connectEvent(100, 0, 110, EXAMPLE_IPV4); + connectEvent(101, 0, 23, EXAMPLE_IPV4); + connectEvent(101, 0, 45, EXAMPLE_IPV6); + connectEvent(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4); + + // netId, type, return code, latency + dnsEvent(100, EVENT_GETADDRINFO, 0, 3456); + dnsEvent(100, EVENT_GETADDRINFO, 3, 45); + dnsEvent(100, EVENT_GETHOSTBYNAME, 0, 638); + dnsEvent(101, EVENT_GETADDRINFO, 0, 56); + dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34); + + // iface, uid + final byte[] mac = {0x48, 0x7c, 0x2b, 0x6a, 0x3e, 0x4b}; + final String srcIp = "192.168.2.1"; + final String dstIp = "192.168.2.23"; + final int sport = 2356; + final int dport = 13489; + final long now = 1001L; + final int v4 = 0x800; + final int tcp = 6; + final int udp = 17; + wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L); + wakeupEvent("wlan0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L); + wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L); + wakeupEvent("wlan0", 10008, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L); + wakeupEvent("wlan0", -1, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L); + wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L); + + long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs; + final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); + final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell); + NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi); + logDefaultNetworkEvent(timeMs + 200L, cellNai, null); + logDefaultNetworkEvent(timeMs + 300L, wifiNai, cellNai); + + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 100", + " transports: 0", + " ip_reachability_event <", + " event_type: 512", + " if_name: \"\"", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 200", + " transports: 0", + " dhcp_event <", + " duration_ms: 192", + " if_name: \"\"", + " state_transition: \"SomeState\"", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 300", + " transports: 0", + " ip_provisioning_event <", + " event_type: 1", + " if_name: \"\"", + " latency_ms: 5678", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 400", + " transports: 0", + " validation_probe_event <", + " latency_ms: 40730", + " probe_result: 204", + " probe_type: 257", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 500", + " transports: 0", + " apf_statistics <", + " dropped_ras: 2", + " duration_ms: 45000", + " matching_ras: 2", + " max_program_size: 2048", + " parse_errors: 2", + " program_updates: 4", + " program_updates_all: 7", + " program_updates_allowing_multicast: 3", + " received_ras: 10", + " total_packet_dropped: 0", + " total_packet_processed: 0", + " zero_lifetime_ras: 1", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 600", + " transports: 0", + " ra_event <", + " dnssl_lifetime: -1", + " prefix_preferred_lifetime: 300", + " prefix_valid_lifetime: 400", + " rdnss_lifetime: 1000", + " route_info_lifetime: -1", + " router_lifetime: 2000", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 5", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " default_network_event <", + " default_network_duration_ms: 200", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", + " no_default_network_duration_ms: 0", + " previous_default_network_link_layer: 0", + " previous_network_ip_support: 0", + " validation_duration_ms: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 100", + " time_ms: 0", + " transports: 1", + " default_network_event <", + " default_network_duration_ms: 100", + " final_score: 50", + " initial_score: 50", + " ip_support: 2", + " no_default_network_duration_ms: 0", + " previous_default_network_link_layer: 0", + " previous_network_ip_support: 0", + " validation_duration_ms: 100", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 100", + " time_ms: 0", + " transports: 2", + " connect_statistics <", + " connect_blocking_count: 1", + " connect_count: 3", + " errnos_counters <", + " key: 11", + " value: 1", + " >", + " ipv6_addr_count: 1", + " latencies_ms: 110", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 101", + " time_ms: 0", + " transports: 1", + " connect_statistics <", + " connect_blocking_count: 2", + " connect_count: 2", + " ipv6_addr_count: 1", + " latencies_ms: 23", + " latencies_ms: 45", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 100", + " time_ms: 0", + " transports: 2", + " dns_lookup_batch <", + " event_types: 1", + " event_types: 1", + " event_types: 2", + " getaddrinfo_error_count: 0", + " getaddrinfo_query_count: 0", + " gethostbyname_error_count: 0", + " gethostbyname_query_count: 0", + " latencies_ms: 3456", + " latencies_ms: 45", + " latencies_ms: 638", + " return_codes: 0", + " return_codes: 3", + " return_codes: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 101", + " time_ms: 0", + " transports: 1", + " dns_lookup_batch <", + " event_types: 1", + " event_types: 2", + " getaddrinfo_error_count: 0", + " getaddrinfo_query_count: 0", + " gethostbyname_error_count: 0", + " gethostbyname_query_count: 0", + " latencies_ms: 56", + " latencies_ms: 34", + " return_codes: 0", + " return_codes: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 3", + " duration_sec: 0", + " ethertype_counts <", + " key: 2048", + " value: 6", + " >", + " ip_next_header_counts <", + " key: 6", + " value: 3", + " >", + " ip_next_header_counts <", + " key: 17", + " value: 3", + " >", + " l2_broadcast_count: 0", + " l2_multicast_count: 0", + " l2_unicast_count: 6", + " no_uid_wakeups: 1", + " non_application_wakeups: 0", + " root_wakeups: 0", + " system_wakeups: 2", + " total_wakeups: 6", + " >", + ">", + "version: 2\n"); + + verifySerialization(want, getdump("flush")); + } + + String getdump(String ... command) { + StringWriter buffer = new StringWriter(); + PrintWriter writer = new PrintWriter(buffer); + mService.impl.dump(null, writer, command); + return buffer.toString(); + } + + private void setCapabilities(int netId) { + final ArgumentCaptor networkCallback = + ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); + verify(mCm).registerNetworkCallback(any(), networkCallback.capture()); + networkCallback.getValue().onCapabilitiesChanged(new Network(netId), + netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL); + } + + void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception { + setCapabilities(netId); + mNetdListener.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1); + } + + void dnsEvent(int netId, int type, int result, int latency) throws Exception { + setCapabilities(netId); + mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0); + } + + void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp, + String dstIp, int sport, int dport, long now) throws Exception { + String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface; + mNetdListener.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now); + } + + NetworkAgentInfo makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports) { + NetworkAgentInfo nai = mock(NetworkAgentInfo.class); + when(nai.network()).thenReturn(new Network(netId)); + when(nai.getCurrentScore()).thenReturn(score); + nai.linkProperties = new LinkProperties(); + nai.networkCapabilities = new NetworkCapabilities(); + nai.lastValidated = true; + for (int t : BitUtils.unpackBits(transports)) { + nai.networkCapabilities.addTransportType(t); + } + if (ipv4) { + nai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.12/24")); + nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"))); + } + if (ipv6) { + nai.linkProperties.addLinkAddress(new LinkAddress("2001:db8:dead:beef:f00::a0/64")); + nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("::/0"))); + } + return nai; + } + + + + static void verifySerialization(String want, String output) { + try { + byte[] got = Base64.decode(output, Base64.DEFAULT); + IpConnectivityLogClass.IpConnectivityLog log = + IpConnectivityLogClass.IpConnectivityLog.parseFrom(got); + assertEquals(want, log.toString()); + } catch (Exception e) { + fail(e.toString()); + } + } +} diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java new file mode 100644 index 0000000000..116d755e30 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.ConnectivityResources; +import android.net.IDnsResolver; +import android.net.INetd; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkProvider; +import android.net.NetworkScore; +import android.os.Binder; +import android.text.format.DateUtils; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.connectivity.resources.R; +import com.android.server.ConnectivityService; +import com.android.server.connectivity.NetworkNotificationManager.NotificationType; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LingerMonitorTest { + static final String CELLULAR = "CELLULAR"; + static final String WIFI = "WIFI"; + + static final long LOW_RATE_LIMIT = DateUtils.MINUTE_IN_MILLIS; + static final long HIGH_RATE_LIMIT = 0; + + static final int LOW_DAILY_LIMIT = 2; + static final int HIGH_DAILY_LIMIT = 1000; + + LingerMonitor mMonitor; + + @Mock ConnectivityService mConnService; + @Mock IDnsResolver mDnsResolver; + @Mock INetd mNetd; + @Mock Context mCtx; + @Mock NetworkNotificationManager mNotifier; + @Mock Resources mResources; + @Mock QosCallbackTracker mQosCallbackTracker; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mCtx.getResources()).thenReturn(mResources); + when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity"); + ConnectivityResources.setResourcesContextForTest(mCtx); + + mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT); + } + + @After + public void tearDown() { + ConnectivityResources.setResourcesContextForTest(null); + } + + @Test + public void testTransitions() { + setNotificationSwitch(transition(WIFI, CELLULAR)); + NetworkAgentInfo nai1 = wifiNai(100); + NetworkAgentInfo nai2 = cellNai(101); + + assertTrue(mMonitor.isNotificationEnabled(nai1, nai2)); + assertFalse(mMonitor.isNotificationEnabled(nai2, nai1)); + } + + @Test + public void testNotificationOnLinger() { + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNotification(from, to); + } + + @Test + public void testToastOnLinger() { + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyToast(from, to); + } + + @Test + public void testNotificationClearedAfterDisconnect() { + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNotification(from, to); + + mMonitor.noteDisconnect(to); + verify(mNotifier, times(1)).clearNotification(100); + } + + @Test + public void testNotificationClearedAfterSwitchingBack() { + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNotification(from, to); + + mMonitor.noteLingerDefaultNetwork(to, from); + verify(mNotifier, times(1)).clearNotification(100); + } + + @Test + public void testUniqueToast() { + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyToast(from, to); + + mMonitor.noteLingerDefaultNetwork(to, from); + verify(mNotifier, times(1)).clearNotification(100); + + reset(mNotifier); + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNoNotifications(); + } + + @Test + public void testMultipleNotifications() { + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); + NetworkAgentInfo wifi1 = wifiNai(100); + NetworkAgentInfo wifi2 = wifiNai(101); + NetworkAgentInfo cell = cellNai(102); + + mMonitor.noteLingerDefaultNetwork(wifi1, cell); + verifyNotification(wifi1, cell); + + mMonitor.noteLingerDefaultNetwork(cell, wifi2); + verify(mNotifier, times(1)).clearNotification(100); + + reset(mNotifier); + mMonitor.noteLingerDefaultNetwork(wifi2, cell); + verifyNotification(wifi2, cell); + } + + @Test + public void testRateLimiting() throws InterruptedException { + mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, LOW_RATE_LIMIT); + + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); + NetworkAgentInfo wifi1 = wifiNai(100); + NetworkAgentInfo wifi2 = wifiNai(101); + NetworkAgentInfo wifi3 = wifiNai(102); + NetworkAgentInfo cell = cellNai(103); + + mMonitor.noteLingerDefaultNetwork(wifi1, cell); + verifyNotification(wifi1, cell); + reset(mNotifier); + + Thread.sleep(50); + mMonitor.noteLingerDefaultNetwork(cell, wifi2); + mMonitor.noteLingerDefaultNetwork(wifi2, cell); + verifyNoNotifications(); + + Thread.sleep(50); + mMonitor.noteLingerDefaultNetwork(cell, wifi3); + mMonitor.noteLingerDefaultNetwork(wifi3, cell); + verifyNoNotifications(); + } + + @Test + public void testDailyLimiting() throws InterruptedException { + mMonitor = new TestableLingerMonitor(mCtx, mNotifier, LOW_DAILY_LIMIT, HIGH_RATE_LIMIT); + + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); + NetworkAgentInfo wifi1 = wifiNai(100); + NetworkAgentInfo wifi2 = wifiNai(101); + NetworkAgentInfo wifi3 = wifiNai(102); + NetworkAgentInfo cell = cellNai(103); + + mMonitor.noteLingerDefaultNetwork(wifi1, cell); + verifyNotification(wifi1, cell); + reset(mNotifier); + + Thread.sleep(50); + mMonitor.noteLingerDefaultNetwork(cell, wifi2); + mMonitor.noteLingerDefaultNetwork(wifi2, cell); + verifyNotification(wifi2, cell); + reset(mNotifier); + + Thread.sleep(50); + mMonitor.noteLingerDefaultNetwork(cell, wifi3); + mMonitor.noteLingerDefaultNetwork(wifi3, cell); + verifyNoNotifications(); + } + + @Test + public void testUniqueNotification() { + setNotificationSwitch(transition(WIFI, CELLULAR)); + setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNotification(from, to); + + mMonitor.noteLingerDefaultNetwork(to, from); + verify(mNotifier, times(1)).clearNotification(100); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNotification(from, to); + } + + @Test + public void testIgnoreNeverValidatedNetworks() { + setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); + setNotificationSwitch(transition(WIFI, CELLULAR)); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + from.everValidated = false; + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNoNotifications(); + } + + @Test + public void testIgnoreCurrentlyValidatedNetworks() { + setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); + setNotificationSwitch(transition(WIFI, CELLULAR)); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + from.lastValidated = true; + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNoNotifications(); + } + + @Test + public void testNoNotificationType() { + setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); + setNotificationSwitch(); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNoNotifications(); + } + + @Test + public void testNoTransitionToNotify() { + setNotificationType(LingerMonitor.NOTIFY_TYPE_NONE); + setNotificationSwitch(transition(WIFI, CELLULAR)); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNoNotifications(); + } + + @Test + public void testDifferentTransitionToNotify() { + setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); + setNotificationSwitch(transition(CELLULAR, WIFI)); + NetworkAgentInfo from = wifiNai(100); + NetworkAgentInfo to = cellNai(101); + + mMonitor.noteLingerDefaultNetwork(from, to); + verifyNoNotifications(); + } + + void setNotificationSwitch(String... transitions) { + when(mResources.getStringArray(R.array.config_networkNotifySwitches)) + .thenReturn(transitions); + } + + String transition(String from, String to) { + return from + "-" + to; + } + + void setNotificationType(int type) { + when(mResources.getInteger(R.integer.config_networkNotifySwitchType)).thenReturn(type); + } + + void verifyNoToast() { + verify(mNotifier, never()).showToast(any(), any()); + } + + void verifyNoNotification() { + verify(mNotifier, never()) + .showNotification(anyInt(), any(), any(), any(), any(), anyBoolean()); + } + + void verifyNoNotifications() { + verifyNoToast(); + verifyNoNotification(); + } + + void verifyToast(NetworkAgentInfo from, NetworkAgentInfo to) { + verifyNoNotification(); + verify(mNotifier, times(1)).showToast(from, to); + } + + void verifyNotification(NetworkAgentInfo from, NetworkAgentInfo to) { + verifyNoToast(); + verify(mNotifier, times(1)).showNotification(eq(from.network.netId), + eq(NotificationType.NETWORK_SWITCH), eq(from), eq(to), any(), eq(true)); + } + + NetworkAgentInfo nai(int netId, int transport, int networkType, String networkTypeName) { + NetworkInfo info = new NetworkInfo(networkType, 0, networkTypeName, ""); + NetworkCapabilities caps = new NetworkCapabilities(); + caps.addCapability(0); + caps.addTransportType(transport); + NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info, + new LinkProperties(), caps, new NetworkScore.Builder().setLegacyInt(50).build(), + mCtx, null, new NetworkAgentConfig.Builder().build(), mConnService, mNetd, + mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), + mQosCallbackTracker, new ConnectivityService.Dependencies()); + nai.everValidated = true; + return nai; + } + + NetworkAgentInfo wifiNai(int netId) { + return nai(netId, NetworkCapabilities.TRANSPORT_WIFI, + ConnectivityManager.TYPE_WIFI, WIFI); + } + + NetworkAgentInfo cellNai(int netId) { + return nai(netId, NetworkCapabilities.TRANSPORT_CELLULAR, + ConnectivityManager.TYPE_MOBILE, CELLULAR); + } + + public static class TestableLingerMonitor extends LingerMonitor { + public TestableLingerMonitor(Context c, NetworkNotificationManager n, int l, long r) { + super(c, n, l, r); + } + @Override protected PendingIntent createNotificationIntent() { + return null; + } + } +} diff --git a/tests/unit/java/com/android/server/connectivity/MetricsTestUtil.java b/tests/unit/java/com/android/server/connectivity/MetricsTestUtil.java new file mode 100644 index 0000000000..5064b9bd91 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/MetricsTestUtil.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.net.ConnectivityMetricsEvent; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.function.Consumer; + +abstract public class MetricsTestUtil { + private MetricsTestUtil() { + } + + static ConnectivityMetricsEvent ev(Parcelable p) { + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.timestamp = 1L; + ev.data = p; + return ev; + } + + static ConnectivityMetricsEvent describeIpEvent(Consumer... fs) { + Parcel p = Parcel.obtain(); + for (Consumer f : fs) { + f.accept(p); + } + p.setDataPosition(0); + return ev(p.readParcelable(ClassLoader.getSystemClassLoader())); + } + + static Consumer aType(Class c) { + return aString(c.getName()); + } + + static Consumer aBool(boolean b) { + return aByte((byte) (b ? 1 : 0)); + } + + static Consumer aByte(byte b) { + return (p) -> p.writeByte(b); + } + + static Consumer anInt(int i) { + return (p) -> p.writeInt(i); + } + + static Consumer aLong(long l) { + return (p) -> p.writeLong(l); + } + + static Consumer aString(String s) { + return (p) -> p.writeString(s); + } + + static Consumer aByteArray(byte... ary) { + return (p) -> p.writeByteArray(ary); + } + + static Consumer anIntArray(int... ary) { + return (p) -> p.writeIntArray(ary); + } + + static byte b(int i) { + return (byte) i; + } +} diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java new file mode 100644 index 0000000000..38f6d7f317 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkPolicy.LIMIT_DISABLED; +import static android.net.NetworkPolicy.SNOOZE_NEVER; +import static android.net.NetworkPolicy.WARNING_DISABLED; +import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES; + +import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH; +import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN; + +import static junit.framework.TestCase.assertNotNull; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.usage.NetworkStatsManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.EthernetNetworkSpecifier; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkPolicy; +import android.net.NetworkPolicyManager; +import android.net.NetworkTemplate; +import android.net.TelephonyNetworkSpecifier; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.test.mock.MockContentResolver; +import android.util.DataUnit; +import android.util.RecurrenceRule; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.LocalServices; +import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.net.NetworkStatsManagerInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.time.Clock; +import java.time.Instant; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class MultipathPolicyTrackerTest { + private static final Network TEST_NETWORK = new Network(123); + private static final int POLICY_SNOOZED = -100; + + @Mock private Context mContext; + @Mock private Context mUserAllContext; + @Mock private Resources mResources; + @Mock private Handler mHandler; + @Mock private MultipathPolicyTracker.Dependencies mDeps; + @Mock private Clock mClock; + @Mock private ConnectivityManager mCM; + @Mock private NetworkPolicyManager mNPM; + @Mock private NetworkStatsManager mStatsManager; + @Mock private NetworkPolicyManagerInternal mNPMI; + @Mock private NetworkStatsManagerInternal mNetworkStatsManagerInternal; + @Mock private TelephonyManager mTelephonyManager; + private MockContentResolver mContentResolver; + + private ArgumentCaptor mConfigChangeReceiverCaptor; + + private MultipathPolicyTracker mTracker; + + private Clock mPreviousRecurrenceRuleClock; + private boolean mRecurrenceRuleClockMocked; + + private void mockService(String serviceName, Class serviceClass, T service) { + when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName); + when(mContext.getSystemService(serviceName)).thenReturn(service); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mPreviousRecurrenceRuleClock = RecurrenceRule.sClock; + RecurrenceRule.sClock = mClock; + mRecurrenceRuleClockMocked = true; + + mConfigChangeReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); + + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo()); + // Mock user id to all users that Context#registerReceiver will register with all users too. + doReturn(UserHandle.ALL.getIdentifier()).when(mUserAllContext).getUserId(); + when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())) + .thenReturn(mUserAllContext); + when(mUserAllContext.registerReceiver(mConfigChangeReceiverCaptor.capture(), + argThat(f -> f.hasAction(ACTION_CONFIGURATION_CHANGED)), any(), any())) + .thenReturn(null); + + when(mDeps.getClock()).thenReturn(mClock); + + when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); + + mContentResolver = Mockito.spy(new MockContentResolver(mContext)); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + Settings.Global.clearProviderForTest(); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + + mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, mCM); + mockService(Context.NETWORK_POLICY_SERVICE, NetworkPolicyManager.class, mNPM); + mockService(Context.NETWORK_STATS_SERVICE, NetworkStatsManager.class, mStatsManager); + mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager); + + LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); + LocalServices.addService(NetworkPolicyManagerInternal.class, mNPMI); + + LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class); + LocalServices.addService(NetworkStatsManagerInternal.class, mNetworkStatsManagerInternal); + + mTracker = new MultipathPolicyTracker(mContext, mHandler, mDeps); + } + + @After + public void tearDown() { + // Avoid setting static clock to null (which should normally not be the case) + // if MockitoAnnotations.initMocks threw an exception + if (mRecurrenceRuleClockMocked) { + RecurrenceRule.sClock = mPreviousRecurrenceRuleClock; + } + mRecurrenceRuleClockMocked = false; + } + + private void setDefaultQuotaGlobalSetting(long setting) { + Settings.Global.putInt(mContentResolver, NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES, + (int) setting); + } + + private void testGetMultipathPreference( + long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit, + long defaultGlobalSetting, long defaultResSetting, boolean roaming) { + + // TODO: tests should not use ZoneId.systemDefault() once code handles TZ correctly. + final ZonedDateTime now = ZonedDateTime.ofInstant( + Instant.parse("2017-04-02T10:11:12Z"), ZoneId.systemDefault()); + final ZonedDateTime startOfDay = now.truncatedTo(ChronoUnit.DAYS); + when(mClock.millis()).thenReturn(now.toInstant().toEpochMilli()); + when(mClock.instant()).thenReturn(now.toInstant()); + when(mClock.getZone()).thenReturn(ZoneId.systemDefault()); + + // Setup plan quota + when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH)) + .thenReturn(subscriptionQuota); + + // Setup user policy warning / limit + if (policyWarning != WARNING_DISABLED || policyLimit != LIMIT_DISABLED) { + final Instant recurrenceStart = Instant.parse("2017-04-01T00:00:00Z"); + final RecurrenceRule recurrenceRule = new RecurrenceRule( + ZonedDateTime.ofInstant( + recurrenceStart, + ZoneId.systemDefault()), + null /* end */, + Period.ofMonths(1)); + final boolean snoozeWarning = policyWarning == POLICY_SNOOZED; + final boolean snoozeLimit = policyLimit == POLICY_SNOOZED; + when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[] { + new NetworkPolicy( + NetworkTemplate.buildTemplateMobileWildcard(), + recurrenceRule, + snoozeWarning ? 0 : policyWarning, + snoozeLimit ? 0 : policyLimit, + snoozeWarning ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER, + snoozeLimit ? recurrenceStart.toEpochMilli() + 1 : SNOOZE_NEVER, + SNOOZE_NEVER, + true /* metered */, + false /* inferred */) + }); + } else { + when(mNPM.getNetworkPolicies()).thenReturn(new NetworkPolicy[0]); + } + + // Setup default quota in settings and resources + if (defaultGlobalSetting > 0) { + setDefaultQuotaGlobalSetting(defaultGlobalSetting); + } + when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes)) + .thenReturn((int) defaultResSetting); + + when(mNetworkStatsManagerInternal.getNetworkTotalBytes( + any(), + eq(startOfDay.toInstant().toEpochMilli()), + eq(now.toInstant().toEpochMilli()))).thenReturn(usedBytesToday); + + ArgumentCaptor networkCallback = + ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); + mTracker.start(); + verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any()); + + // Simulate callback after capability changes + NetworkCapabilities capabilities = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addTransportType(TRANSPORT_CELLULAR) + .setNetworkSpecifier(new EthernetNetworkSpecifier("eth234")); + if (!roaming) { + capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING); + } + networkCallback.getValue().onCapabilitiesChanged( + TEST_NETWORK, + capabilities); + + // make sure it also works with the new introduced TelephonyNetworkSpecifier + capabilities = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addTransportType(TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() + .setSubscriptionId(234).build()); + if (!roaming) { + capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING); + } + networkCallback.getValue().onCapabilitiesChanged( + TEST_NETWORK, + capabilities); + } + + @Test + public void testGetMultipathPreference_SubscriptionQuota() { + testGetMultipathPreference( + DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, + DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */, + DataUnit.MEGABYTES.toBytes(100) /* policyWarning */, + LIMIT_DISABLED, + DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, + 2_500_000 /* defaultResSetting */, + false /* roaming */); + + verify(mStatsManager, times(1)).registerUsageCallback( + any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); + } + + @Test + public void testGetMultipathPreference_UserWarningQuota() { + testGetMultipathPreference( + DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, + OPPORTUNISTIC_QUOTA_UNKNOWN, + // 29 days from Apr. 2nd to May 1st + DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyWarning */, + LIMIT_DISABLED, + DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, + 2_500_000 /* defaultResSetting */, + false /* roaming */); + + // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB + verify(mStatsManager, times(1)).registerUsageCallback( + any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); + } + + @Test + public void testGetMultipathPreference_SnoozedWarningQuota() { + testGetMultipathPreference( + DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, + OPPORTUNISTIC_QUOTA_UNKNOWN, + // 29 days from Apr. 2nd to May 1st + POLICY_SNOOZED /* policyWarning */, + DataUnit.MEGABYTES.toBytes(15 * 29 * 20) /* policyLimit */, + DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, + 2_500_000 /* defaultResSetting */, + false /* roaming */); + + // Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB + verify(mStatsManager, times(1)).registerUsageCallback( + any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); + } + + @Test + public void testGetMultipathPreference_SnoozedBothQuota() { + testGetMultipathPreference( + DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */, + OPPORTUNISTIC_QUOTA_UNKNOWN, + // 29 days from Apr. 2nd to May 1st + POLICY_SNOOZED /* policyWarning */, + POLICY_SNOOZED /* policyLimit */, + DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */, + 2_500_000 /* defaultResSetting */, + false /* roaming */); + + // Default global setting should be used: 12 - 7 = 5 + verify(mStatsManager, times(1)).registerUsageCallback( + any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any()); + } + + @Test + public void testGetMultipathPreference_SettingChanged() { + testGetMultipathPreference( + DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, + OPPORTUNISTIC_QUOTA_UNKNOWN, + WARNING_DISABLED, + LIMIT_DISABLED, + -1 /* defaultGlobalSetting */, + DataUnit.MEGABYTES.toBytes(10) /* defaultResSetting */, + false /* roaming */); + + verify(mStatsManager, times(1)).registerUsageCallback( + any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any()); + + // Update setting + setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14)); + mTracker.mSettingsObserver.onChange( + false, Settings.Global.getUriFor(NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES)); + + // Callback must have been re-registered with new setting + verify(mStatsManager, times(1)).unregisterUsageCallback(any()); + verify(mStatsManager, times(1)).registerUsageCallback( + any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); + } + + @Test + public void testGetMultipathPreference_ResourceChanged() { + testGetMultipathPreference( + DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */, + OPPORTUNISTIC_QUOTA_UNKNOWN, + WARNING_DISABLED, + LIMIT_DISABLED, + -1 /* defaultGlobalSetting */, + DataUnit.MEGABYTES.toBytes(14) /* defaultResSetting */, + false /* roaming */); + + verify(mStatsManager, times(1)).registerUsageCallback( + any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any()); + + when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes)) + .thenReturn((int) DataUnit.MEGABYTES.toBytes(16)); + + final BroadcastReceiver configChangeReceiver = mConfigChangeReceiverCaptor.getValue(); + assertNotNull(configChangeReceiver); + configChangeReceiver.onReceive(mContext, new Intent()); + + // Uses the new setting (16 - 2 = 14MB) + verify(mStatsManager, times(1)).registerUsageCallback( + any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any()); + } +} diff --git a/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java new file mode 100644 index 0000000000..9b2a638f8b --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.ConnectivityManager; +import android.net.IDnsResolver; +import android.net.INetd; +import android.net.InterfaceConfigurationParcel; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.NetworkAgentConfig; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.test.TestLooper; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.ConnectivityService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class Nat464XlatTest { + + static final String BASE_IFACE = "test0"; + static final String STACKED_IFACE = "v4-test0"; + static final LinkAddress V6ADDR = new LinkAddress("2001:db8:1::f00/64"); + static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29"); + static final String NAT64_PREFIX = "64:ff9b::/96"; + static final String OTHER_NAT64_PREFIX = "2001:db8:0:64::/96"; + static final int NETID = 42; + + @Mock ConnectivityService mConnectivity; + @Mock IDnsResolver mDnsResolver; + @Mock INetd mNetd; + @Mock NetworkAgentInfo mNai; + + TestLooper mLooper; + Handler mHandler; + NetworkAgentConfig mAgentConfig = new NetworkAgentConfig(); + + Nat464Xlat makeNat464Xlat(boolean isCellular464XlatEnabled) { + return new Nat464Xlat(mNai, mNetd, mDnsResolver, new ConnectivityService.Dependencies()) { + @Override protected int getNetId() { + return NETID; + } + + @Override protected boolean isCellular464XlatEnabled() { + return isCellular464XlatEnabled; + } + }; + } + + private void markNetworkConnected() { + mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", ""); + } + + private void markNetworkDisconnected() { + mNai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, "", ""); + } + + @Before + public void setUp() throws Exception { + mLooper = new TestLooper(); + mHandler = new Handler(mLooper.getLooper()); + + MockitoAnnotations.initMocks(this); + + mNai.linkProperties = new LinkProperties(); + mNai.linkProperties.setInterfaceName(BASE_IFACE); + mNai.networkInfo = new NetworkInfo(null); + mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI); + mNai.networkCapabilities = new NetworkCapabilities(); + markNetworkConnected(); + when(mNai.connService()).thenReturn(mConnectivity); + when(mNai.netAgentConfig()).thenReturn(mAgentConfig); + when(mNai.handler()).thenReturn(mHandler); + final InterfaceConfigurationParcel mConfig = new InterfaceConfigurationParcel(); + when(mNetd.interfaceGetCfg(eq(STACKED_IFACE))).thenReturn(mConfig); + mConfig.ipv4Addr = ADDR.getAddress().getHostAddress(); + mConfig.prefixLength = ADDR.getPrefixLength(); + } + + private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) { + Nat464Xlat nat = makeNat464Xlat(true); + String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b " + + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), + nai.networkInfo.getDetailedState(), + mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), + nai.linkProperties.getLinkAddresses()); + assertEquals(msg, expected, nat.requiresClat(nai)); + } + + private void assertShouldStartClat(boolean expected, NetworkAgentInfo nai) { + Nat464Xlat nat = makeNat464Xlat(true); + String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b " + + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(), + nai.networkInfo.getDetailedState(), + mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(), + nai.linkProperties.getLinkAddresses()); + assertEquals(msg, expected, nat.shouldStartClat(nai)); + } + + @Test + public void testRequiresClat() throws Exception { + final int[] supportedTypes = { + ConnectivityManager.TYPE_MOBILE, + ConnectivityManager.TYPE_WIFI, + ConnectivityManager.TYPE_ETHERNET, + }; + + // NetworkInfo doesn't allow setting the State directly, but rather + // requires setting DetailedState in order set State as a side-effect. + final NetworkInfo.DetailedState[] supportedDetailedStates = { + NetworkInfo.DetailedState.CONNECTED, + NetworkInfo.DetailedState.SUSPENDED, + }; + + LinkProperties oldLp = new LinkProperties(mNai.linkProperties); + for (int type : supportedTypes) { + mNai.networkInfo.setType(type); + for (NetworkInfo.DetailedState state : supportedDetailedStates) { + mNai.networkInfo.setDetailedState(state, "reason", "extraInfo"); + + mNai.linkProperties.setNat64Prefix(new IpPrefix(OTHER_NAT64_PREFIX)); + assertRequiresClat(false, mNai); + assertShouldStartClat(false, mNai); + + mNai.linkProperties.addLinkAddress(new LinkAddress("fc00::1/64")); + assertRequiresClat(false, mNai); + assertShouldStartClat(false, mNai); + + mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); + assertRequiresClat(true, mNai); + assertShouldStartClat(true, mNai); + + mAgentConfig.skip464xlat = true; + assertRequiresClat(false, mNai); + assertShouldStartClat(false, mNai); + + mAgentConfig.skip464xlat = false; + assertRequiresClat(true, mNai); + assertShouldStartClat(true, mNai); + + mNai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.2/24")); + assertRequiresClat(false, mNai); + assertShouldStartClat(false, mNai); + + mNai.linkProperties.removeLinkAddress(new LinkAddress("192.0.2.2/24")); + assertRequiresClat(true, mNai); + assertShouldStartClat(true, mNai); + + mNai.linkProperties.setNat64Prefix(null); + assertRequiresClat(true, mNai); + assertShouldStartClat(false, mNai); + + mNai.linkProperties = new LinkProperties(oldLp); + } + } + } + + private void makeClatUnnecessary(boolean dueToDisconnect) { + if (dueToDisconnect) { + markNetworkDisconnected(); + } else { + mNai.linkProperties.addLinkAddress(ADDR); + } + } + + private void checkNormalStartAndStop(boolean dueToDisconnect) throws Exception { + Nat464Xlat nat = makeNat464Xlat(true); + ArgumentCaptor c = ArgumentCaptor.forClass(LinkProperties.class); + + mNai.linkProperties.addLinkAddress(V6ADDR); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); + + // Start clat. + nat.start(); + + verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); + + // Stacked interface up notification arrives. + nat.interfaceLinkStateChanged(STACKED_IFACE, true); + mLooper.dispatchNext(); + + verify(mNetd).interfaceGetCfg(eq(STACKED_IFACE)); + verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); + assertFalse(c.getValue().getStackedLinks().isEmpty()); + assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertRunning(nat); + + // Stop clat (Network disconnects, IPv4 addr appears, ...). + makeClatUnnecessary(dueToDisconnect); + nat.stop(); + + verify(mNetd).clatdStop(eq(BASE_IFACE)); + verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); + assertTrue(c.getValue().getStackedLinks().isEmpty()); + assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); + assertIdle(nat); + + // Stacked interface removed notification arrives and is ignored. + nat.interfaceRemoved(STACKED_IFACE); + mLooper.dispatchNext(); + + verifyNoMoreInteractions(mNetd, mConnectivity); + } + + @Test + public void testNormalStartAndStopDueToDisconnect() throws Exception { + checkNormalStartAndStop(true); + } + + @Test + public void testNormalStartAndStopDueToIpv4Addr() throws Exception { + checkNormalStartAndStop(false); + } + + private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception { + Nat464Xlat nat = makeNat464Xlat(true); + ArgumentCaptor c = ArgumentCaptor.forClass(LinkProperties.class); + InOrder inOrder = inOrder(mNetd, mConnectivity); + + mNai.linkProperties.addLinkAddress(V6ADDR); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); + + nat.start(); + + inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); + + // Stacked interface up notification arrives. + nat.interfaceLinkStateChanged(STACKED_IFACE, true); + mLooper.dispatchNext(); + + inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); + assertFalse(c.getValue().getStackedLinks().isEmpty()); + assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertRunning(nat); + + // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...). + nat.stop(); + + inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE)); + + inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); + assertTrue(c.getValue().getStackedLinks().isEmpty()); + assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertIdle(nat); + + if (interfaceRemovedFirst) { + // Stacked interface removed notification arrives and is ignored. + nat.interfaceRemoved(STACKED_IFACE); + mLooper.dispatchNext(); + nat.interfaceLinkStateChanged(STACKED_IFACE, false); + mLooper.dispatchNext(); + } + + assertTrue(c.getValue().getStackedLinks().isEmpty()); + assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertIdle(nat); + inOrder.verifyNoMoreInteractions(); + + nat.start(); + + inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); + + if (!interfaceRemovedFirst) { + // Stacked interface removed notification arrives and is ignored. + nat.interfaceRemoved(STACKED_IFACE); + mLooper.dispatchNext(); + nat.interfaceLinkStateChanged(STACKED_IFACE, false); + mLooper.dispatchNext(); + } + + // Stacked interface up notification arrives. + nat.interfaceLinkStateChanged(STACKED_IFACE, true); + mLooper.dispatchNext(); + + inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture()); + assertFalse(c.getValue().getStackedLinks().isEmpty()); + assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertRunning(nat); + + // ConnectivityService stops clat again. + nat.stop(); + + inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE)); + + inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); + assertTrue(c.getValue().getStackedLinks().isEmpty()); + assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertIdle(nat); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testStartStopStart() throws Exception { + checkStartStopStart(true); + } + + @Test + public void testStartStopStartBeforeInterfaceRemoved() throws Exception { + checkStartStopStart(false); + } + + @Test + public void testClatdCrashWhileRunning() throws Exception { + Nat464Xlat nat = makeNat464Xlat(true); + ArgumentCaptor c = ArgumentCaptor.forClass(LinkProperties.class); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); + + nat.start(); + + verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); + + // Stacked interface up notification arrives. + nat.interfaceLinkStateChanged(STACKED_IFACE, true); + mLooper.dispatchNext(); + + verify(mNetd).interfaceGetCfg(eq(STACKED_IFACE)); + verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture()); + assertFalse(c.getValue().getStackedLinks().isEmpty()); + assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertRunning(nat); + + // Stacked interface removed notification arrives (clatd crashed, ...). + nat.interfaceRemoved(STACKED_IFACE); + mLooper.dispatchNext(); + + verify(mNetd).clatdStop(eq(BASE_IFACE)); + verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); + assertTrue(c.getValue().getStackedLinks().isEmpty()); + assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertIdle(nat); + + // ConnectivityService stops clat: no-op. + nat.stop(); + + verifyNoMoreInteractions(mNetd, mConnectivity); + } + + private void checkStopBeforeClatdStarts(boolean dueToDisconnect) throws Exception { + Nat464Xlat nat = makeNat464Xlat(true); + + mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); + + nat.start(); + + verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); + + // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) + makeClatUnnecessary(dueToDisconnect); + nat.stop(); + + verify(mNetd).clatdStop(eq(BASE_IFACE)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); + assertIdle(nat); + + // In-flight interface up notification arrives: no-op + nat.interfaceLinkStateChanged(STACKED_IFACE, true); + mLooper.dispatchNext(); + + // Interface removed notification arrives after stopClatd() takes effect: no-op. + nat.interfaceRemoved(STACKED_IFACE); + mLooper.dispatchNext(); + + assertIdle(nat); + + verifyNoMoreInteractions(mNetd, mConnectivity); + } + + @Test + public void testStopDueToDisconnectBeforeClatdStarts() throws Exception { + checkStopBeforeClatdStarts(true); + } + + @Test + public void testStopDueToIpv4AddrBeforeClatdStarts() throws Exception { + checkStopBeforeClatdStarts(false); + } + + private void checkStopAndClatdNeverStarts(boolean dueToDisconnect) throws Exception { + Nat464Xlat nat = makeNat464Xlat(true); + + mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); + + nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX)); + + nat.start(); + + verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX)); + + // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) + makeClatUnnecessary(dueToDisconnect); + nat.stop(); + + verify(mNetd).clatdStop(eq(BASE_IFACE)); + verify(mDnsResolver).stopPrefix64Discovery(eq(NETID)); + assertIdle(nat); + + verifyNoMoreInteractions(mNetd, mConnectivity); + } + + @Test + public void testStopDueToDisconnectAndClatdNeverStarts() throws Exception { + checkStopAndClatdNeverStarts(true); + } + + @Test + public void testStopDueToIpv4AddressAndClatdNeverStarts() throws Exception { + checkStopAndClatdNeverStarts(false); + } + + @Test + public void testNat64PrefixPreference() throws Exception { + final IpPrefix prefixFromDns = new IpPrefix(NAT64_PREFIX); + final IpPrefix prefixFromRa = new IpPrefix(OTHER_NAT64_PREFIX); + + Nat464Xlat nat = makeNat464Xlat(true); + + final LinkProperties emptyLp = new LinkProperties(); + LinkProperties fixedupLp; + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromDns(prefixFromDns); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromDns, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromRa(prefixFromRa); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromRa(null); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromDns, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromRa(prefixFromRa); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromDns(null); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(prefixFromRa, fixedupLp.getNat64Prefix()); + + fixedupLp = new LinkProperties(); + nat.setNat64PrefixFromRa(null); + nat.fixupLinkProperties(emptyLp, fixedupLp); + assertEquals(null, fixedupLp.getNat64Prefix()); + } + + private void checkClatDisabledOnCellular(boolean onCellular) throws Exception { + // Disable 464xlat on cellular networks. + Nat464Xlat nat = makeNat464Xlat(false); + mNai.linkProperties.addLinkAddress(V6ADDR); + mNai.networkCapabilities.setTransportType(TRANSPORT_CELLULAR, onCellular); + nat.update(); + + final IpPrefix nat64Prefix = new IpPrefix(NAT64_PREFIX); + if (onCellular) { + // Prefix discovery is never started. + verify(mDnsResolver, never()).startPrefix64Discovery(eq(NETID)); + assertIdle(nat); + + // If a NAT64 prefix comes in from an RA, clat is not started either. + mNai.linkProperties.setNat64Prefix(nat64Prefix); + nat.setNat64PrefixFromRa(nat64Prefix); + nat.update(); + verify(mNetd, never()).clatdStart(anyString(), anyString()); + assertIdle(nat); + } else { + // Prefix discovery is started. + verify(mDnsResolver).startPrefix64Discovery(eq(NETID)); + assertIdle(nat); + + // If a NAT64 prefix comes in from an RA, clat is started. + mNai.linkProperties.setNat64Prefix(nat64Prefix); + nat.setNat64PrefixFromRa(nat64Prefix); + nat.update(); + verify(mNetd).clatdStart(BASE_IFACE, NAT64_PREFIX); + assertStarting(nat); + } + } + + @Test + public void testClatDisabledOnCellular() throws Exception { + checkClatDisabledOnCellular(true); + } + + @Test + public void testClatDisabledOnNonCellular() throws Exception { + checkClatDisabledOnCellular(false); + } + + static void assertIdle(Nat464Xlat nat) { + assertTrue("Nat464Xlat was not IDLE", !nat.isStarted()); + } + + static void assertStarting(Nat464Xlat nat) { + assertTrue("Nat464Xlat was not STARTING", nat.isStarting()); + } + + static void assertRunning(Nat464Xlat nat) { + assertTrue("Nat464Xlat was not RUNNING", nat.isRunning()); + } +} diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java new file mode 100644 index 0000000000..50aaaee244 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO; +import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; + +import static com.android.testutils.MiscAsserts.assertStringContains; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.system.OsConstants; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Base64; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; +import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetdEventListenerServiceTest { + private static final String EXAMPLE_IPV4 = "192.0.2.1"; + private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1"; + + private static final byte[] MAC_ADDR = + {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b}; + + NetdEventListenerService mService; + ConnectivityManager mCm; + private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .build(); + private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .build(); + + @Before + public void setUp() { + mCm = mock(ConnectivityManager.class); + mService = new NetdEventListenerService(mCm); + } + + @Test + public void testWakeupEventLogging() throws Exception { + final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH; + final long now = System.currentTimeMillis(); + final String iface = "wlan0"; + final byte[] mac = MAC_ADDR; + final String srcIp = "192.168.2.1"; + final String dstIp = "192.168.2.23"; + final String srcIp6 = "2001:db8:4:fd00:a585:13d1:6a23:4fb4"; + final String dstIp6 = "2001:db8:4006:807::200a"; + final int sport = 2356; + final int dport = 13489; + + final int v4 = 0x800; + final int v6 = 0x86dd; + final int tcp = 6; + final int udp = 17; + final int icmp6 = 58; + + // Baseline without any event + String[] baseline = listNetdEvent(); + + int[] uids = {10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004}; + wakeupEvent(iface, uids[0], v4, tcp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent(iface, uids[1], v6, udp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent(iface, uids[2], v6, udp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent(iface, uids[3], v4, icmp6, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent(iface, uids[4], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent(iface, uids[5], v4, tcp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent(iface, uids[6], v6, udp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent(iface, uids[7], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent(iface, uids[8], v6, udp, mac, srcIp6, dstIp6, sport, dport, now); + + String[] events2 = remove(listNetdEvent(), baseline); + int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line + assertEquals(expectedLength2, events2.length); + assertStringContains(events2[0], "WakeupStats"); + assertStringContains(events2[0], "wlan0"); + assertStringContains(events2[0], "0x800"); + assertStringContains(events2[0], "0x86dd"); + for (int i = 0; i < uids.length; i++) { + String got = events2[i+1]; + assertStringContains(got, "WakeupEvent"); + assertStringContains(got, "wlan0"); + assertStringContains(got, "uid: " + uids[i]); + } + + int uid = 20000; + for (int i = 0; i < BUFFER_LENGTH * 2; i++) { + long ts = now + 10; + wakeupEvent(iface, uid, 0x800, 6, mac, srcIp, dstIp, 23, 24, ts); + } + + String[] events3 = remove(listNetdEvent(), baseline); + int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line + assertEquals(expectedLength3, events3.length); + assertStringContains(events2[0], "WakeupStats"); + assertStringContains(events2[0], "wlan0"); + for (int i = 1; i < expectedLength3; i++) { + String got = events3[i]; + assertStringContains(got, "WakeupEvent"); + assertStringContains(got, "wlan0"); + assertStringContains(got, "uid: " + uid); + } + + uid = 45678; + wakeupEvent(iface, uid, 0x800, 6, mac, srcIp, dstIp, 23, 24, now); + + String[] events4 = remove(listNetdEvent(), baseline); + String lastEvent = events4[events4.length - 1]; + assertStringContains(lastEvent, "WakeupEvent"); + assertStringContains(lastEvent, "wlan0"); + assertStringContains(lastEvent, "uid: " + uid); + } + + @Test + public void testWakeupStatsLogging() throws Exception { + final byte[] mac = MAC_ADDR; + final String srcIp = "192.168.2.1"; + final String dstIp = "192.168.2.23"; + final String srcIp6 = "2401:fa00:4:fd00:a585:13d1:6a23:4fb4"; + final String dstIp6 = "2404:6800:4006:807::200a"; + final int sport = 2356; + final int dport = 13489; + final long now = 1001L; + + final int v4 = 0x800; + final int v6 = 0x86dd; + final int tcp = 6; + final int udp = 17; + final int icmp6 = 58; + + wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent("rmnet0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent("rmnet0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent("rmnet0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent("wlan0", 10004, v4, udp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent("wlan0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent("rmnet0", 10052, v4, tcp, mac, srcIp, dstIp, sport, dport, now); + wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent("rmnet0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now); + wakeupEvent("wlan0", 1010, v4, udp, mac, srcIp, dstIp, sport, dport, now); + + String got = flushStatistics(); + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 3", + " duration_sec: 0", + " ethertype_counts <", + " key: 2048", + " value: 4", + " >", + " ethertype_counts <", + " key: 34525", + " value: 1", + " >", + " ip_next_header_counts <", + " key: 6", + " value: 5", + " >", + " l2_broadcast_count: 0", + " l2_multicast_count: 0", + " l2_unicast_count: 5", + " no_uid_wakeups: 0", + " non_application_wakeups: 0", + " root_wakeups: 0", + " system_wakeups: 2", + " total_wakeups: 5", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 2", + " duration_sec: 0", + " ethertype_counts <", + " key: 2048", + " value: 5", + " >", + " ethertype_counts <", + " key: 34525", + " value: 5", + " >", + " ip_next_header_counts <", + " key: 6", + " value: 3", + " >", + " ip_next_header_counts <", + " key: 17", + " value: 5", + " >", + " ip_next_header_counts <", + " key: 58", + " value: 2", + " >", + " l2_broadcast_count: 0", + " l2_multicast_count: 0", + " l2_unicast_count: 10", + " no_uid_wakeups: 2", + " non_application_wakeups: 1", + " root_wakeups: 2", + " system_wakeups: 3", + " total_wakeups: 10", + " >", + ">", + "version: 2\n"); + assertEquals(want, got); + } + + @Test + public void testDnsLogging() throws Exception { + asyncDump(100); + + dnsEvent(100, EVENT_GETADDRINFO, 0, 3456); + dnsEvent(100, EVENT_GETADDRINFO, 0, 267); + dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230); + dnsEvent(100, EVENT_GETADDRINFO, 3, 45); + dnsEvent(100, EVENT_GETADDRINFO, 1, 2111); + dnsEvent(100, EVENT_GETADDRINFO, 0, 450); + dnsEvent(100, EVENT_GETHOSTBYNAME, 200, 638); + dnsEvent(100, EVENT_GETHOSTBYNAME, 178, 1300); + dnsEvent(101, EVENT_GETADDRINFO, 0, 56); + dnsEvent(101, EVENT_GETADDRINFO, 0, 78); + dnsEvent(101, EVENT_GETADDRINFO, 0, 14); + dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 56); + dnsEvent(101, EVENT_GETADDRINFO, 0, 78); + dnsEvent(101, EVENT_GETADDRINFO, 0, 14); + + String got = flushStatistics(); + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 100", + " time_ms: 0", + " transports: 2", + " dns_lookup_batch <", + " event_types: 1", + " event_types: 1", + " event_types: 2", + " event_types: 1", + " event_types: 1", + " event_types: 1", + " event_types: 2", + " event_types: 2", + " getaddrinfo_error_count: 0", + " getaddrinfo_query_count: 0", + " gethostbyname_error_count: 0", + " gethostbyname_query_count: 0", + " latencies_ms: 3456", + " latencies_ms: 267", + " latencies_ms: 1230", + " latencies_ms: 45", + " latencies_ms: 2111", + " latencies_ms: 450", + " latencies_ms: 638", + " latencies_ms: 1300", + " return_codes: 0", + " return_codes: 0", + " return_codes: 22", + " return_codes: 3", + " return_codes: 1", + " return_codes: 0", + " return_codes: 200", + " return_codes: 178", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 101", + " time_ms: 0", + " transports: 1", + " dns_lookup_batch <", + " event_types: 1", + " event_types: 1", + " event_types: 1", + " event_types: 2", + " event_types: 1", + " event_types: 1", + " getaddrinfo_error_count: 0", + " getaddrinfo_query_count: 0", + " gethostbyname_error_count: 0", + " gethostbyname_query_count: 0", + " latencies_ms: 56", + " latencies_ms: 78", + " latencies_ms: 14", + " latencies_ms: 56", + " latencies_ms: 78", + " latencies_ms: 14", + " return_codes: 0", + " return_codes: 0", + " return_codes: 0", + " return_codes: 0", + " return_codes: 0", + " return_codes: 0", + " >", + ">", + "version: 2\n"); + assertEquals(want, got); + } + + @Test + public void testConnectLogging() throws Exception { + asyncDump(100); + + final int OK = 0; + Thread[] logActions = { + // ignored + connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4), + connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV6), + connectEventAction(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4), + connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), + connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), + // valid latencies + connectEventAction(100, OK, 110, EXAMPLE_IPV4), + connectEventAction(100, OK, 23, EXAMPLE_IPV4), + connectEventAction(100, OK, 45, EXAMPLE_IPV4), + connectEventAction(101, OK, 56, EXAMPLE_IPV4), + connectEventAction(101, OK, 523, EXAMPLE_IPV6), + connectEventAction(101, OK, 214, EXAMPLE_IPV6), + connectEventAction(101, OK, 67, EXAMPLE_IPV6), + // errors + connectEventAction(100, OsConstants.EPERM, 0, EXAMPLE_IPV4), + connectEventAction(101, OsConstants.EPERM, 0, EXAMPLE_IPV4), + connectEventAction(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4), + connectEventAction(100, OsConstants.EACCES, 0, EXAMPLE_IPV4), + connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV4), + connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV6), + connectEventAction(100, OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4), + connectEventAction(101, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4), + connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), + connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), + connectEventAction(101, OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4), + }; + + for (Thread t : logActions) { + t.start(); + } + for (Thread t : logActions) { + t.join(); + } + + String got = flushStatistics(); + String want = String.join("\n", + "dropped_events: 0", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 100", + " time_ms: 0", + " transports: 2", + " connect_statistics <", + " connect_blocking_count: 3", + " connect_count: 6", + " errnos_counters <", + " key: 1", + " value: 1", + " >", + " errnos_counters <", + " key: 11", + " value: 1", + " >", + " errnos_counters <", + " key: 13", + " value: 1", + " >", + " errnos_counters <", + " key: 98", + " value: 1", + " >", + " errnos_counters <", + " key: 110", + " value: 2", + " >", + " ipv6_addr_count: 1", + " latencies_ms: 23", + " latencies_ms: 45", + " latencies_ms: 110", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 2", + " network_id: 101", + " time_ms: 0", + " transports: 1", + " connect_statistics <", + " connect_blocking_count: 4", + " connect_count: 6", + " errnos_counters <", + " key: 1", + " value: 1", + " >", + " errnos_counters <", + " key: 13", + " value: 2", + " >", + " errnos_counters <", + " key: 110", + " value: 1", + " >", + " errnos_counters <", + " key: 111", + " value: 1", + " >", + " ipv6_addr_count: 5", + " latencies_ms: 56", + " latencies_ms: 67", + " latencies_ms: 214", + " latencies_ms: 523", + " >", + ">", + "version: 2\n"); + assertEquals(want, got); + } + + private void setCapabilities(int netId) { + final ArgumentCaptor networkCallback = + ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); + verify(mCm).registerNetworkCallback(any(), networkCallback.capture()); + networkCallback.getValue().onCapabilitiesChanged(new Network(netId), + netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL); + } + + Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) { + setCapabilities(netId); + return new Thread(() -> { + try { + mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1); + } catch (Exception e) { + fail(e.toString()); + } + }); + } + + void dnsEvent(int netId, int type, int result, int latency) throws Exception { + setCapabilities(netId); + mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0); + } + + void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp, + String dstIp, int sport, int dport, long now) throws Exception { + String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface; + mService.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now); + } + + void asyncDump(long durationMs) throws Exception { + final long stop = System.currentTimeMillis() + durationMs; + final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); + new Thread(() -> { + while (System.currentTimeMillis() < stop) { + mService.list(pw); + } + }).start(); + } + + // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. + String flushStatistics() throws Exception { + IpConnectivityMetrics metricsService = + new IpConnectivityMetrics(mock(Context.class), (ctx) -> 2000); + metricsService.mNetdListener = mService; + + StringWriter buffer = new StringWriter(); + PrintWriter writer = new PrintWriter(buffer); + metricsService.impl.dump(null, writer, new String[]{"flush"}); + byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT); + IpConnectivityLog log = IpConnectivityLog.parseFrom(bytes); + for (IpConnectivityEvent ev : log.events) { + if (ev.getConnectStatistics() == null) { + continue; + } + // Sort repeated fields of connect() events arriving in non-deterministic order. + Arrays.sort(ev.getConnectStatistics().latenciesMs); + Arrays.sort(ev.getConnectStatistics().errnosCounters, + Comparator.comparingInt((p) -> p.key)); + } + return log.toString(); + } + + String[] listNetdEvent() throws Exception { + StringWriter buffer = new StringWriter(); + PrintWriter writer = new PrintWriter(buffer); + mService.list(writer); + return buffer.toString().split("\\n"); + } + + static T[] remove(T[] array, T[] filtered) { + List c = Arrays.asList(filtered); + int next = 0; + for (int i = 0; i < array.length; i++) { + if (c.contains(array[i])) { + continue; + } + array[next++] = array[i]; + } + return Arrays.copyOf(array, next); + } +} diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java new file mode 100644 index 0000000000..3adf08c199 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.ConnectivityResources; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.os.UserHandle; +import android.telephony.TelephonyManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.connectivity.resources.R; +import com.android.server.connectivity.NetworkNotificationManager.NotificationType; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkNotificationManagerTest { + + private static final String TEST_SSID = "Test SSID"; + private static final String TEST_EXTRA_INFO = "extra"; + static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities(); + static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities(); + static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities(); + static { + CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + + WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + WIFI_CAPABILITIES.setSSID(TEST_SSID); + + // Set the underyling network to wifi. + VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN); + VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); + } + + @Mock Context mCtx; + @Mock Resources mResources; + @Mock PackageManager mPm; + @Mock TelephonyManager mTelephonyManager; + @Mock NotificationManager mNotificationManager; + @Mock NetworkAgentInfo mWifiNai; + @Mock NetworkAgentInfo mCellNai; + @Mock NetworkAgentInfo mVpnNai; + @Mock NetworkInfo mNetworkInfo; + ArgumentCaptor mCaptor; + + NetworkNotificationManager mManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mCaptor = ArgumentCaptor.forClass(Notification.class); + mWifiNai.networkCapabilities = WIFI_CAPABILITIES; + mWifiNai.networkInfo = mNetworkInfo; + mCellNai.networkCapabilities = CELL_CAPABILITIES; + mCellNai.networkInfo = mNetworkInfo; + mVpnNai.networkCapabilities = VPN_CAPABILITIES; + mVpnNai.networkInfo = mNetworkInfo; + doReturn(true).when(mVpnNai).isVPN(); + when(mCtx.getResources()).thenReturn(mResources); + when(mCtx.getPackageManager()).thenReturn(mPm); + when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo()); + final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mCtx)); + doReturn(UserHandle.ALL).when(asUserCtx).getUser(); + when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx); + when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE))) + .thenReturn(mNotificationManager); + when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO); + ConnectivityResources.setResourcesContextForTest(mCtx); + when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B); + + // Come up with some credible-looking transport names. The actual values do not matter. + String[] transportNames = new String[NetworkCapabilities.MAX_TRANSPORT + 1]; + for (int transport = 0; transport <= NetworkCapabilities.MAX_TRANSPORT; transport++) { + transportNames[transport] = NetworkCapabilities.transportNameOf(transport); + } + when(mResources.getStringArray(R.array.network_switch_type_name)) + .thenReturn(transportNames); + + mManager = new NetworkNotificationManager(mCtx, mTelephonyManager); + } + + @After + public void tearDown() { + ConnectivityResources.setResourcesContextForTest(null); + } + + private void verifyTitleByNetwork(final int id, final NetworkAgentInfo nai, final int title) { + final String tag = NetworkNotificationManager.tagFor(id); + mManager.showNotification(id, PRIVATE_DNS_BROKEN, nai, null, null, true); + verify(mNotificationManager, times(1)) + .notify(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any()); + final int transportType = NetworkNotificationManager.approximateTransportType(nai); + if (transportType == NetworkCapabilities.TRANSPORT_WIFI) { + verify(mResources, times(1)).getString(eq(title), eq(TEST_EXTRA_INFO)); + } else { + verify(mResources, times(1)).getString(title); + } + verify(mResources, times(1)).getString(eq(R.string.private_dns_broken_detailed)); + } + + @Test + public void testTitleOfPrivateDnsBroken() { + // Test the title of mobile data. + verifyTitleByNetwork(100, mCellNai, R.string.mobile_no_internet); + clearInvocations(mResources); + + // Test the title of wifi. + verifyTitleByNetwork(101, mWifiNai, R.string.wifi_no_internet); + clearInvocations(mResources); + + // Test the title of other networks. + verifyTitleByNetwork(102, mVpnNai, R.string.other_networks_no_internet); + clearInvocations(mResources); + } + + @Test + public void testNotificationsShownAndCleared() { + final int NETWORK_ID_BASE = 100; + List types = Arrays.asList(NotificationType.values()); + List ids = new ArrayList<>(types.size()); + for (int i = 0; i < types.size(); i++) { + ids.add(NETWORK_ID_BASE + i); + } + Collections.shuffle(ids); + Collections.shuffle(types); + + for (int i = 0; i < ids.size(); i++) { + mManager.showNotification(ids.get(i), types.get(i), mWifiNai, mCellNai, null, false); + } + + List idsToClear = new ArrayList<>(ids); + Collections.shuffle(idsToClear); + for (int i = 0; i < ids.size(); i++) { + mManager.clearNotification(idsToClear.get(i)); + } + + for (int i = 0; i < ids.size(); i++) { + final int id = ids.get(i); + final int eventId = types.get(i).eventId; + final String tag = NetworkNotificationManager.tagFor(id); + verify(mNotificationManager, times(1)).notify(eq(tag), eq(eventId), any()); + verify(mNotificationManager, times(1)).cancel(eq(tag), eq(eventId)); + } + } + + @Test + @Ignore + // Ignored because the code under test calls Log.wtf, which crashes the tests on eng builds. + // TODO: re-enable after fixing this (e.g., turn Log.wtf into exceptions that this test catches) + public void testNoInternetNotificationsNotShownForCellular() { + mManager.showNotification(100, NO_INTERNET, mCellNai, mWifiNai, null, false); + mManager.showNotification(101, LOST_INTERNET, mCellNai, mWifiNai, null, false); + + verify(mNotificationManager, never()).notify(any(), anyInt(), any()); + + mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false); + + final int eventId = NO_INTERNET.eventId; + final String tag = NetworkNotificationManager.tagFor(102); + verify(mNotificationManager, times(1)).notify(eq(tag), eq(eventId), any()); + } + + @Test + public void testNotificationsNotShownIfNoInternetCapability() { + mWifiNai.networkCapabilities = new NetworkCapabilities(); + mWifiNai.networkCapabilities .addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false); + mManager.showNotification(103, LOST_INTERNET, mWifiNai, mCellNai, null, false); + mManager.showNotification(104, NETWORK_SWITCH, mWifiNai, mCellNai, null, false); + + verify(mNotificationManager, never()).notify(any(), anyInt(), any()); + } + + @Test + public void testDuplicatedNotificationsNoInternetThenSignIn() { + final int id = 101; + final String tag = NetworkNotificationManager.tagFor(id); + + // Show first NO_INTERNET + mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)).notify(eq(tag), eq(NO_INTERNET.eventId), any()); + + // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET + mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)).cancel(eq(tag), eq(NO_INTERNET.eventId)); + verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any()); + + // Network disconnects + mManager.clearNotification(id); + verify(mNotificationManager, times(1)).cancel(eq(tag), eq(SIGN_IN.eventId)); + } + + @Test + public void testDuplicatedNotificationsSignInThenNoInternet() { + final int id = 101; + final String tag = NetworkNotificationManager.tagFor(id); + + // Show first SIGN_IN + mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any()); + reset(mNotificationManager); + + // NO_INTERNET arrives after, but is ignored. + mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, never()).cancel(any(), anyInt()); + verify(mNotificationManager, never()).notify(any(), anyInt(), any()); + + // Network disconnects + mManager.clearNotification(id); + verify(mNotificationManager, times(1)).cancel(eq(tag), eq(SIGN_IN.eventId)); + } + + @Test + public void testClearNotificationByType() { + final int id = 101; + final String tag = NetworkNotificationManager.tagFor(id); + + // clearNotification(int id, NotificationType notifyType) will check if given type is equal + // to previous type or not. If they are equal then clear the notification; if they are not + // equal then return. + mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)).notify(eq(tag), eq(NO_INTERNET.eventId), any()); + + // Previous notification is NO_INTERNET and given type is NO_INTERNET too. The notification + // should be cleared. + mManager.clearNotification(id, NO_INTERNET); + verify(mNotificationManager, times(1)).cancel(eq(tag), eq(NO_INTERNET.eventId)); + + // SIGN_IN is popped-up. + mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false); + verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any()); + + // The notification type is not matching previous one, PARTIAL_CONNECTIVITY won't be + // cleared. + mManager.clearNotification(id, PARTIAL_CONNECTIVITY); + verify(mNotificationManager, never()).cancel(eq(tag), eq(PARTIAL_CONNECTIVITY.eventId)); + } +} diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt new file mode 100644 index 0000000000..86c91165f6 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt @@ -0,0 +1,84 @@ +/* + * 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.server.connectivity + +import android.net.NetworkRequest +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import kotlin.test.assertEquals +import kotlin.test.assertNull + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkRankerTest { + private val ranker = NetworkRanker() + + private fun makeNai(satisfy: Boolean, score: Int) = mock(NetworkAgentInfo::class.java).also { + doReturn(satisfy).`when`(it).satisfies(any()) + doReturn(score).`when`(it).currentScore + } + + @Test + fun testGetBestNetwork() { + val scores = listOf(20, 50, 90, 60, 23, 68) + val nais = scores.map { makeNai(true, it) } + val bestNetwork = nais[2] // The one with the top score + val someRequest = mock(NetworkRequest::class.java) + assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testIgnoreNonSatisfying() { + val nais = listOf(makeNai(true, 20), makeNai(true, 50), makeNai(false, 90), + makeNai(false, 60), makeNai(true, 23), makeNai(false, 68)) + val bestNetwork = nais[1] // Top score that's satisfying + val someRequest = mock(NetworkRequest::class.java) + assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testNoMatch() { + val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90)) + val someRequest = mock(NetworkRequest::class.java) + assertNull(ranker.getBestNetwork(someRequest, nais)) + } + + @Test + fun testEmpty() { + val someRequest = mock(NetworkRequest::class.java) + assertNull(ranker.getBestNetwork(someRequest, emptyList())) + } + + // Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST + // network satisfying the request if multiple of them have the same score. + @Test + fun testStable() { + val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30), + makeNai(true, 30), makeNai(true, 30), makeNai(true, 30)) + val someRequest = mock(NetworkRequest::class.java) + assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1)) + + val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20), + makeNai(true, 50), makeNai(true, 50), makeNai(true, 40)) + assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2)) + } +} diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java new file mode 100644 index 0000000000..02a58080fe --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -0,0 +1,803 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.connectivity; + +import static android.Manifest.permission.CHANGE_NETWORK_STATE; +import static android.Manifest.permission.CHANGE_WIFI_STATE; +import static android.Manifest.permission.CONNECTIVITY_INTERNAL; +import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; +import static android.Manifest.permission.INTERNET; +import static android.Manifest.permission.NETWORK_STACK; +import static android.Manifest.permission.UPDATE_DEVICE_STATS; +import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_OEM; +import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT; +import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR; +import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; +import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED; +import static android.content.pm.PackageManager.GET_PERMISSIONS; +import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static android.os.Process.SYSTEM_UID; + +import static com.android.server.connectivity.PermissionMonitor.NETWORK; +import static com.android.server.connectivity.PermissionMonitor.SYSTEM; + +import static junit.framework.Assert.fail; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.AdditionalMatchers.aryEq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.INetd; +import android.net.UidRange; +import android.net.Uri; +import android.os.Build; +import android.os.SystemConfigManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.SparseIntArray; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class PermissionMonitorTest { + private static final UserHandle MOCK_USER1 = UserHandle.of(0); + private static final UserHandle MOCK_USER2 = UserHandle.of(1); + private static final int MOCK_UID1 = 10001; + private static final int MOCK_UID2 = 10086; + private static final int SYSTEM_UID1 = 1000; + private static final int SYSTEM_UID2 = 1008; + private static final int VPN_UID = 10002; + private static final String REAL_SYSTEM_PACKAGE_NAME = "android"; + private static final String MOCK_PACKAGE1 = "appName1"; + private static final String MOCK_PACKAGE2 = "appName2"; + private static final String SYSTEM_PACKAGE1 = "sysName1"; + private static final String SYSTEM_PACKAGE2 = "sysName2"; + private static final String PARTITION_SYSTEM = "system"; + private static final String PARTITION_OEM = "oem"; + private static final String PARTITION_PRODUCT = "product"; + private static final String PARTITION_VENDOR = "vendor"; + private static final int VERSION_P = Build.VERSION_CODES.P; + private static final int VERSION_Q = Build.VERSION_CODES.Q; + + @Mock private Context mContext; + @Mock private PackageManager mPackageManager; + @Mock private INetd mNetdService; + @Mock private UserManager mUserManager; + @Mock private PermissionMonitor.Dependencies mDeps; + @Mock private SystemConfigManager mSystemConfigManager; + + private PermissionMonitor mPermissionMonitor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); + when(mUserManager.getUserHandles(eq(true))).thenReturn( + Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 })); + when(mContext.getSystemServiceName(SystemConfigManager.class)) + .thenReturn(Context.SYSTEM_CONFIG_SERVICE); + when(mContext.getSystemService(Context.SYSTEM_CONFIG_SERVICE)) + .thenReturn(mSystemConfigManager); + when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]); + final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mContext)); + doReturn(UserHandle.ALL).when(asUserCtx).getUser(); + when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx); + + mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps)); + + when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(/* empty app list */ null); + mPermissionMonitor.startMonitoring(); + } + + private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion, int uid, + String... permissions) { + final PackageInfo packageInfo = + packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, permissions, partition); + packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion; + packageInfo.applicationInfo.uid = uid; + return mPermissionMonitor.hasRestrictedNetworkPermission(packageInfo); + } + + private static PackageInfo systemPackageInfoWithPermissions(String... permissions) { + return packageInfoWithPermissions( + REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM); + } + + private static PackageInfo vendorPackageInfoWithPermissions(String... permissions) { + return packageInfoWithPermissions( + REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_VENDOR); + } + + private static PackageInfo packageInfoWithPermissions(int permissionsFlags, + String[] permissions, String partition) { + int[] requestedPermissionsFlags = new int[permissions.length]; + for (int i = 0; i < permissions.length; i++) { + requestedPermissionsFlags[i] = permissionsFlags; + } + final PackageInfo packageInfo = new PackageInfo(); + packageInfo.requestedPermissions = permissions; + packageInfo.applicationInfo = new ApplicationInfo(); + packageInfo.requestedPermissionsFlags = requestedPermissionsFlags; + int privateFlags = 0; + switch (partition) { + case PARTITION_OEM: + privateFlags = PRIVATE_FLAG_OEM; + break; + case PARTITION_PRODUCT: + privateFlags = PRIVATE_FLAG_PRODUCT; + break; + case PARTITION_VENDOR: + privateFlags = PRIVATE_FLAG_VENDOR; + break; + } + packageInfo.applicationInfo.privateFlags = privateFlags; + return packageInfo; + } + + private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, + UserHandle user) { + final PackageInfo pkgInfo; + if (hasSystemPermission) { + pkgInfo = systemPackageInfoWithPermissions( + CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS); + } else { + pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, ""); + } + pkgInfo.applicationInfo.uid = user.getUid(UserHandle.getAppId(uid)); + return pkgInfo; + } + + @Test + public void testHasPermission() { + PackageInfo app = systemPackageInfoWithPermissions(); + assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); + assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK)); + assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL)); + + app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE, NETWORK_STACK); + assertTrue(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); + assertTrue(mPermissionMonitor.hasPermission(app, NETWORK_STACK)); + assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL)); + + app = systemPackageInfoWithPermissions( + CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL); + assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); + assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK)); + assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL)); + + app = packageInfoWithPermissions(REQUESTED_PERMISSION_REQUIRED, new String[] { + CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL, NETWORK_STACK }, + PARTITION_SYSTEM); + assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); + assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK)); + assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL)); + + app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE); + app.requestedPermissions = null; + assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); + + app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE); + app.requestedPermissionsFlags = null; + assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE)); + } + + @Test + public void testIsVendorApp() { + PackageInfo app = systemPackageInfoWithPermissions(); + assertFalse(mPermissionMonitor.isVendorApp(app.applicationInfo)); + app = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, + new String[] {}, PARTITION_OEM); + assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo)); + app = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, + new String[] {}, PARTITION_PRODUCT); + assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo)); + app = vendorPackageInfoWithPermissions(); + assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo)); + } + + @Test + public void testHasNetworkPermission() { + PackageInfo app = systemPackageInfoWithPermissions(); + assertFalse(mPermissionMonitor.hasNetworkPermission(app)); + app = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE); + assertTrue(mPermissionMonitor.hasNetworkPermission(app)); + app = systemPackageInfoWithPermissions(NETWORK_STACK); + assertFalse(mPermissionMonitor.hasNetworkPermission(app)); + app = systemPackageInfoWithPermissions(CONNECTIVITY_USE_RESTRICTED_NETWORKS); + assertFalse(mPermissionMonitor.hasNetworkPermission(app)); + app = systemPackageInfoWithPermissions(CONNECTIVITY_INTERNAL); + assertFalse(mPermissionMonitor.hasNetworkPermission(app)); + } + + @Test + public void testHasRestrictedNetworkPermission() { + assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1)); + assertFalse(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_P, MOCK_UID1, NETWORK_STACK)); + assertFalse(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + assertFalse(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE)); + + assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1)); + assertFalse(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_Q, MOCK_UID1, CONNECTIVITY_INTERNAL)); + } + + @Test + public void testHasRestrictedNetworkPermissionSystemUid() { + doReturn(VERSION_P).when(mDeps).getDeviceFirstSdkInt(); + assertTrue(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_INTERNAL)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + + doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt(); + assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID)); + assertFalse(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_INTERNAL)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + } + + @Test + public void testHasRestrictedNetworkPermissionVendorApp() { + assertTrue(hasRestrictedNetworkPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_VENDOR, VERSION_P, MOCK_UID1, NETWORK_STACK)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + assertTrue(hasRestrictedNetworkPermission( + PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE)); + + assertFalse(hasRestrictedNetworkPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1)); + assertFalse(hasRestrictedNetworkPermission( + PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CONNECTIVITY_INTERNAL)); + assertFalse(hasRestrictedNetworkPermission( + PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_NETWORK_STATE)); + } + + private void assertBackgroundPermission(boolean hasPermission, String name, int uid, + String... permissions) throws Exception { + when(mPackageManager.getPackageInfo(eq(name), anyInt())) + .thenReturn(packageInfoWithPermissions( + REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM)); + mPermissionMonitor.onPackageAdded(name, uid); + assertEquals(hasPermission, mPermissionMonitor.hasUseBackgroundNetworksPermission(uid)); + } + + @Test + public void testHasUseBackgroundNetworksPermission() throws Exception { + assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(SYSTEM_UID)); + assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID); + assertBackgroundPermission(false, SYSTEM_PACKAGE1, SYSTEM_UID, CONNECTIVITY_INTERNAL); + assertBackgroundPermission(true, SYSTEM_PACKAGE1, SYSTEM_UID, CHANGE_NETWORK_STATE); + assertBackgroundPermission(true, SYSTEM_PACKAGE1, SYSTEM_UID, NETWORK_STACK); + + assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID1)); + assertBackgroundPermission(false, MOCK_PACKAGE1, MOCK_UID1); + assertBackgroundPermission(true, MOCK_PACKAGE1, MOCK_UID1, + CONNECTIVITY_USE_RESTRICTED_NETWORKS); + + assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID2)); + assertBackgroundPermission(false, MOCK_PACKAGE2, MOCK_UID2); + assertBackgroundPermission(false, MOCK_PACKAGE2, MOCK_UID2, + CONNECTIVITY_INTERNAL); + assertBackgroundPermission(true, MOCK_PACKAGE2, MOCK_UID2, NETWORK_STACK); + } + + private class NetdMonitor { + private final HashMap mApps = new HashMap<>(); + + NetdMonitor(INetd mockNetd) throws Exception { + // Add hook to verify and track result of setPermission. + doAnswer((InvocationOnMock invocation) -> { + final Object[] args = invocation.getArguments(); + final Boolean isSystem = args[0].equals(INetd.PERMISSION_SYSTEM); + for (final int uid : (int[]) args[1]) { + // TODO: Currently, permission monitor will send duplicate commands for each uid + // corresponding to each user. Need to fix that and uncomment below test. + // if (mApps.containsKey(uid) && mApps.get(uid) == isSystem) { + // fail("uid " + uid + " is already set to " + isSystem); + // } + mApps.put(uid, isSystem); + } + return null; + }).when(mockNetd).networkSetPermissionForUser(anyInt(), any(int[].class)); + + // Add hook to verify and track result of clearPermission. + doAnswer((InvocationOnMock invocation) -> { + final Object[] args = invocation.getArguments(); + for (final int uid : (int[]) args[0]) { + // TODO: Currently, permission monitor will send duplicate commands for each uid + // corresponding to each user. Need to fix that and uncomment below test. + // if (!mApps.containsKey(uid)) { + // fail("uid " + uid + " does not exist."); + // } + mApps.remove(uid); + } + return null; + }).when(mockNetd).networkClearPermissionForUser(any(int[].class)); + } + + public void expectPermission(Boolean permission, UserHandle[] users, int[] apps) { + for (final UserHandle user : users) { + for (final int app : apps) { + final int uid = user.getUid(app); + if (!mApps.containsKey(uid)) { + fail("uid " + uid + " does not exist."); + } + if (mApps.get(uid) != permission) { + fail("uid " + uid + " has wrong permission: " + permission); + } + } + } + } + + public void expectNoPermission(UserHandle[] users, int[] apps) { + for (final UserHandle user : users) { + for (final int app : apps) { + final int uid = user.getUid(app); + if (mApps.containsKey(uid)) { + fail("uid " + uid + " has listed permissions, expected none."); + } + } + } + } + } + + @Test + public void testUserAndPackageAddRemove() throws Exception { + final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService); + + // MOCK_UID1: MOCK_PACKAGE1 only has network permission. + // SYSTEM_UID: SYSTEM_PACKAGE1 has system permission. + // SYSTEM_UID: SYSTEM_PACKAGE2 only has network permission. + doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(eq(SYSTEM), anyString()); + doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(any(), + eq(SYSTEM_PACKAGE1)); + doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(), + eq(SYSTEM_PACKAGE2)); + doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(), + eq(MOCK_PACKAGE1)); + + // Add SYSTEM_PACKAGE2, expect only have network permission. + mPermissionMonitor.onUserAdded(MOCK_USER1); + addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + + // Add SYSTEM_PACKAGE1, expect permission escalate. + addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + + mPermissionMonitor.onUserAdded(MOCK_USER2); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID}); + + addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID}); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{MOCK_UID1}); + + // Remove MOCK_UID1, expect no permission left for all user. + mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); + removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{MOCK_UID1}); + + // Remove SYSTEM_PACKAGE1, expect permission downgrade. + when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2}); + removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + SYSTEM_PACKAGE1, SYSTEM_UID); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID}); + + mPermissionMonitor.onUserRemoved(MOCK_USER1); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER2}, new int[]{SYSTEM_UID}); + + // Remove all packages, expect no permission left. + when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{}); + removePackageForUsers(new UserHandle[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID, MOCK_UID1}); + + // Remove last user, expect no redundant clearPermission is invoked. + mPermissionMonitor.onUserRemoved(MOCK_USER2); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID, MOCK_UID1}); + } + + @Test + public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception { + when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn( + Arrays.asList(new PackageInfo[] { + buildPackageInfo(true /* hasSystemPermission */, SYSTEM_UID1, MOCK_USER1), + buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1), + buildPackageInfo(false /* hasSystemPermission */, MOCK_UID2, MOCK_USER1), + buildPackageInfo(false /* hasSystemPermission */, VPN_UID, MOCK_USER1) + })); + when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), + eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn( + buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1)); + mPermissionMonitor.startMonitoring(); + // Every app on user 0 except MOCK_UID2 are under VPN. + final Set vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] { + new UidRange(0, MOCK_UID2 - 1), + new UidRange(MOCK_UID2 + 1, UserHandle.PER_USER_RANGE - 1)})); + final Set vpnRange2 = Collections.singleton(new UidRange(MOCK_UID2, MOCK_UID2)); + + // When VPN is connected, expect a rule to be set up for user app MOCK_UID1 + mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID); + verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), + aryEq(new int[] {MOCK_UID1})); + + reset(mNetdService); + + // When MOCK_UID1 package is uninstalled and reinstalled, expect Netd to be updated + mPermissionMonitor.onPackageRemoved( + MOCK_PACKAGE1, MOCK_USER1.getUid(MOCK_UID1)); + verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1})); + mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_USER1.getUid(MOCK_UID1)); + verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), + aryEq(new int[] {MOCK_UID1})); + + reset(mNetdService); + + // During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the + // old UID rules then adds the new ones. Expect netd to be updated + mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID); + verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1})); + mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID); + verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), + aryEq(new int[] {MOCK_UID2})); + + reset(mNetdService); + + // When VPN is disconnected, expect rules to be torn down + mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID); + verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID2})); + assertNull(mPermissionMonitor.getVpnUidRanges("tun0")); + } + + @Test + public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception { + when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn( + Arrays.asList(new PackageInfo[] { + buildPackageInfo(true /* hasSystemPermission */, SYSTEM_UID1, MOCK_USER1), + buildPackageInfo(false /* hasSystemPermission */, VPN_UID, MOCK_USER1) + })); + when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), + eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn( + buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1)); + + mPermissionMonitor.startMonitoring(); + final Set vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1)); + mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange, VPN_UID); + + // Newly-installed package should have uid rules added + mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_USER1.getUid(MOCK_UID1)); + verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), + aryEq(new int[] {MOCK_UID1})); + + // Removed package should have its uid rules removed + mPermissionMonitor.onPackageRemoved( + MOCK_PACKAGE1, MOCK_USER1.getUid(MOCK_UID1)); + verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1})); + } + + + // Normal package add/remove operations will trigger multiple intent for uids corresponding to + // each user. To simulate generic package operations, the onPackageAdded/Removed will need to be + // called multiple times with the uid corresponding to each user. + private void addPackageForUsers(UserHandle[] users, String packageName, int uid) { + for (final UserHandle user : users) { + mPermissionMonitor.onPackageAdded(packageName, user.getUid(uid)); + } + } + + private void removePackageForUsers(UserHandle[] users, String packageName, int uid) { + for (final UserHandle user : users) { + mPermissionMonitor.onPackageRemoved(packageName, user.getUid(uid)); + } + } + + private class NetdServiceMonitor { + private final HashMap mPermissions = new HashMap<>(); + + NetdServiceMonitor(INetd mockNetdService) throws Exception { + // Add hook to verify and track result of setPermission. + doAnswer((InvocationOnMock invocation) -> { + final Object[] args = invocation.getArguments(); + final int permission = (int) args[0]; + for (final int uid : (int[]) args[1]) { + mPermissions.put(uid, permission); + } + return null; + }).when(mockNetdService).trafficSetNetPermForUids(anyInt(), any(int[].class)); + } + + public void expectPermission(int permission, int[] apps) { + for (final int app : apps) { + if (!mPermissions.containsKey(app)) { + fail("uid " + app + " does not exist."); + } + if (mPermissions.get(app) != permission) { + fail("uid " + app + " has wrong permission: " + mPermissions.get(app)); + } + } + } + } + + @Test + public void testPackagePermissionUpdate() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + // MOCK_UID1: MOCK_PACKAGE1 only has internet permission. + // MOCK_UID2: MOCK_PACKAGE2 does not have any permission. + // SYSTEM_UID1: SYSTEM_PACKAGE1 has internet permission and update device stats permission. + // SYSTEM_UID2: SYSTEM_PACKAGE2 has only update device stats permission. + + SparseIntArray netdPermissionsAppIds = new SparseIntArray(); + netdPermissionsAppIds.put(MOCK_UID1, INetd.PERMISSION_INTERNET); + netdPermissionsAppIds.put(MOCK_UID2, INetd.PERMISSION_NONE); + netdPermissionsAppIds.put(SYSTEM_UID1, INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS); + netdPermissionsAppIds.put(SYSTEM_UID2, INetd.PERMISSION_UPDATE_DEVICE_STATS); + + // Send the permission information to netd, expect permission updated. + mPermissionMonitor.sendPackagePermissionsToNetd(netdPermissionsAppIds); + + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, + new int[]{MOCK_UID1}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID2}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{SYSTEM_UID1}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UPDATE_DEVICE_STATS, + new int[]{SYSTEM_UID2}); + + // Update permission of MOCK_UID1, expect new permission show up. + mPermissionMonitor.sendPackagePermissionsForUid(MOCK_UID1, + INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1}); + + // Change permissions of SYSTEM_UID2, expect new permission show up and old permission + // revoked. + mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID2, + INetd.PERMISSION_INTERNET); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{SYSTEM_UID2}); + + // Revoke permission from SYSTEM_UID1, expect no permission stored. + mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID1, INetd.PERMISSION_NONE); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{SYSTEM_UID1}); + } + + private PackageInfo setPackagePermissions(String packageName, int uid, String[] permissions) + throws Exception { + PackageInfo packageInfo = packageInfoWithPermissions( + REQUESTED_PERMISSION_GRANTED, permissions, PARTITION_SYSTEM); + when(mPackageManager.getPackageInfo(eq(packageName), anyInt())).thenReturn(packageInfo); + when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[]{packageName}); + return packageInfo; + } + + private PackageInfo addPackage(String packageName, int uid, String[] permissions) + throws Exception { + PackageInfo packageInfo = setPackagePermissions(packageName, uid, permissions); + mPermissionMonitor.onPackageAdded(packageName, uid); + return packageInfo; + } + + @Test + public void testPackageInstall() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + + addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1}); + + addPackage(MOCK_PACKAGE2, MOCK_UID2, new String[] {INTERNET}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID2}); + } + + @Test + public void testPackageInstallSharedUid() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + + PackageInfo packageInfo1 = addPackage(MOCK_PACKAGE1, MOCK_UID1, + new String[] {INTERNET, UPDATE_DEVICE_STATS}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1}); + + // Install another package with the same uid and no permissions should not cause the UID to + // lose permissions. + PackageInfo packageInfo2 = systemPackageInfoWithPermissions(); + when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2); + when(mPackageManager.getPackagesForUid(MOCK_UID1)) + .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2}); + mPermissionMonitor.onPackageAdded(MOCK_PACKAGE2, MOCK_UID1); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1}); + } + + @Test + public void testPackageUninstallBasic() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + + addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1}); + + when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{}); + mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1}); + } + + @Test + public void testPackageRemoveThenAdd() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + + addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1}); + + when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{}); + mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1}); + + addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1}); + } + + @Test + public void testPackageUpdate() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + + addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID1}); + + addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1}); + } + + @Test + public void testPackageUninstallWithMultiplePackages() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + + addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS}); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1}); + + // Mock another package with the same uid but different permissions. + PackageInfo packageInfo2 = systemPackageInfoWithPermissions(INTERNET); + when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2); + when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{ + MOCK_PACKAGE2}); + + mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1}); + } + + @Test + public void testRealSystemPermission() throws Exception { + // Use the real context as this test must ensure the *real* system package holds the + // necessary permission. + final Context realContext = InstrumentationRegistry.getContext(); + final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService); + final PackageManager manager = realContext.getPackageManager(); + final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME, + GET_PERMISSIONS | MATCH_ANY_USER); + assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + } + + @Test + public void testUpdateUidPermissionsFromSystemConfig() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(new ArrayList<>()); + when(mSystemConfigManager.getSystemPermissionUids(eq(INTERNET))) + .thenReturn(new int[]{ MOCK_UID1, MOCK_UID2 }); + when(mSystemConfigManager.getSystemPermissionUids(eq(UPDATE_DEVICE_STATS))) + .thenReturn(new int[]{ MOCK_UID2 }); + + mPermissionMonitor.startMonitoring(); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{ MOCK_UID1 }); + mNetdServiceMonitor.expectPermission( + INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS, + new int[]{ MOCK_UID2 }); + } + + @Test + public void testIntentReceiver() throws Exception { + final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService); + final ArgumentCaptor receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(), any(), any(), any()); + final BroadcastReceiver receiver = receiverCaptor.getValue(); + + // Verify receiving PACKAGE_ADDED intent. + final Intent addedIntent = new Intent(Intent.ACTION_PACKAGE_ADDED, + Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */)); + addedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID1); + setPackagePermissions(MOCK_PACKAGE1, MOCK_UID1, + new String[] { INTERNET, UPDATE_DEVICE_STATS }); + receiver.onReceive(mContext, addedIntent); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET + | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[] { MOCK_UID1 }); + + // Verify receiving PACKAGE_REMOVED intent. + when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(null); + final Intent removedIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED, + Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */)); + removedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID1); + receiver.onReceive(mContext, removedIntent); + mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[] { MOCK_UID1 }); + } + +} diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java new file mode 100644 index 0000000000..b725b826b1 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java @@ -0,0 +1,1283 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import static android.content.pm.UserInfo.FLAG_ADMIN; +import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; +import static android.content.pm.UserInfo.FLAG_PRIMARY; +import static android.content.pm.UserInfo.FLAG_RESTRICTED; +import static android.net.ConnectivityManager.NetworkCallback; +import static android.net.INetd.IF_STATE_DOWN; +import static android.net.INetd.IF_STATE_UP; +import static android.os.UserHandle.PER_USER_RANGE; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.AppOpsManager; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.INetd; +import android.net.Ikev2VpnProfile; +import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; +import android.net.IpPrefix; +import android.net.IpSecManager; +import android.net.IpSecTunnelInterfaceResponse; +import android.net.LinkProperties; +import android.net.LocalSocket; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo.DetailedState; +import android.net.RouteInfo; +import android.net.UidRangeParcel; +import android.net.VpnManager; +import android.net.VpnService; +import android.net.VpnTransportInfo; +import android.net.ipsec.ike.IkeSessionCallback; +import android.net.ipsec.ike.exceptions.IkeProtocolException; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.ConditionVariable; +import android.os.INetworkManagementService; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.test.TestLooper; +import android.provider.Settings; +import android.security.Credentials; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Range; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.server.IpSecService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.AdditionalAnswers; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +/** + * Tests for {@link Vpn}. + * + * Build, install and run with: + * runtest frameworks-net -c com.android.server.connectivity.VpnTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VpnTest { + private static final String TAG = "VpnTest"; + + // Mock users + static final UserInfo primaryUser = new UserInfo(27, "Primary", FLAG_ADMIN | FLAG_PRIMARY); + static final UserInfo secondaryUser = new UserInfo(15, "Secondary", FLAG_ADMIN); + static final UserInfo restrictedProfileA = new UserInfo(40, "RestrictedA", FLAG_RESTRICTED); + static final UserInfo restrictedProfileB = new UserInfo(42, "RestrictedB", FLAG_RESTRICTED); + static final UserInfo managedProfileA = new UserInfo(45, "ManagedA", FLAG_MANAGED_PROFILE); + static { + restrictedProfileA.restrictedProfileParentId = primaryUser.id; + restrictedProfileB.restrictedProfileParentId = secondaryUser.id; + managedProfileA.profileGroupId = primaryUser.id; + } + + static final Network EGRESS_NETWORK = new Network(101); + static final String EGRESS_IFACE = "wlan0"; + static final String TEST_VPN_PKG = "com.testvpn.vpn"; + private static final String TEST_VPN_SERVER = "1.2.3.4"; + private static final String TEST_VPN_IDENTITY = "identity"; + private static final byte[] TEST_VPN_PSK = "psk".getBytes(); + + private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE); + private static final String TEST_IFACE_NAME = "TEST_IFACE"; + private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345; + private static final long TEST_TIMEOUT_MS = 500L; + + /** + * Names and UIDs for some fake packages. Important points: + * - UID is ordered increasing. + * - One pair of packages have consecutive UIDs. + */ + static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"}; + static final int[] PKG_UIDS = {66, 77, 78, 400}; + + // Mock packages + static final Map mPackages = new ArrayMap<>(); + static { + for (int i = 0; i < PKGS.length; i++) { + mPackages.put(PKGS[i], PKG_UIDS[i]); + } + } + private static final Range PRI_USER_RANGE = uidRangeForUser(primaryUser.id); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; + @Mock private UserManager mUserManager; + @Mock private PackageManager mPackageManager; + @Mock private INetworkManagementService mNetService; + @Mock private INetd mNetd; + @Mock private AppOpsManager mAppOps; + @Mock private NotificationManager mNotificationManager; + @Mock private Vpn.SystemServices mSystemServices; + @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator; + @Mock private ConnectivityManager mConnectivityManager; + @Mock private IpSecService mIpSecService; + @Mock private VpnProfileStore mVpnProfileStore; + private final VpnProfile mVpnProfile; + + private IpSecManager mIpSecManager; + + public VpnTest() throws Exception { + // Build an actual VPN profile that is capable of being converted to and from an + // Ikev2VpnProfile + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY); + builder.setAuthPsk(TEST_VPN_PSK); + mVpnProfile = builder.build().toVpnProfile(); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mIpSecManager = new IpSecManager(mContext, mIpSecService); + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + setMockedPackages(mPackages); + + when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG); + when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG); + when(mContext.getSystemServiceName(UserManager.class)) + .thenReturn(Context.USER_SERVICE); + when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); + when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); + when(mContext.getSystemServiceName(NotificationManager.class)) + .thenReturn(Context.NOTIFICATION_SERVICE); + when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE))) + .thenReturn(mNotificationManager); + when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))) + .thenReturn(mConnectivityManager); + when(mContext.getSystemServiceName(eq(ConnectivityManager.class))) + .thenReturn(Context.CONNECTIVITY_SERVICE); + when(mContext.getSystemService(eq(Context.IPSEC_SERVICE))).thenReturn(mIpSecManager); + when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)) + .thenReturn(Resources.getSystem().getString( + R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) + .thenReturn(true); + + // Used by {@link Notification.Builder} + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; + when(mContext.getApplicationInfo()).thenReturn(applicationInfo); + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); + + doNothing().when(mNetService).registerObserver(any()); + + // Deny all appops by default. + when(mAppOps.noteOpNoThrow(anyString(), anyInt(), anyString(), any(), any())) + .thenReturn(AppOpsManager.MODE_IGNORED); + + // Setup IpSecService + final IpSecTunnelInterfaceResponse tunnelResp = + new IpSecTunnelInterfaceResponse( + IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME); + when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any())) + .thenReturn(tunnelResp); + } + + private Set> rangeSet(Range ... ranges) { + final Set> range = new ArraySet<>(); + for (Range r : ranges) range.add(r); + + return range; + } + + private static Range uidRangeForUser(int userId) { + return new Range(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); + } + + private Range uidRange(int start, int stop) { + return new Range(start, stop); + } + + @Test + public void testRestrictedProfilesAreAddedToVpn() { + setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB); + + final Vpn vpn = createVpn(primaryUser.id); + + // Assume the user can have restricted profiles. + doReturn(true).when(mUserManager).canHaveRestrictedProfile(); + final Set> ranges = + vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, null, null); + + assertEquals(rangeSet(PRI_USER_RANGE, uidRangeForUser(restrictedProfileA.id)), ranges); + } + + @Test + public void testManagedProfilesAreNotAddedToVpn() { + setMockedUsers(primaryUser, managedProfileA); + + final Vpn vpn = createVpn(primaryUser.id); + final Set> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + null, null); + + assertEquals(rangeSet(PRI_USER_RANGE), ranges); + } + + @Test + public void testAddUserToVpnOnlyAddsOneUser() { + setMockedUsers(primaryUser, restrictedProfileA, managedProfileA); + + final Vpn vpn = createVpn(primaryUser.id); + final Set> ranges = new ArraySet<>(); + vpn.addUserToRanges(ranges, primaryUser.id, null, null); + + assertEquals(rangeSet(PRI_USER_RANGE), ranges); + } + + @Test + public void testUidAllowAndDenylist() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + final Range user = PRI_USER_RANGE; + final int userStart = user.getLower(); + final int userStop = user.getUpper(); + final String[] packages = {PKGS[0], PKGS[1], PKGS[2]}; + + // Allowed list + final Set> allow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + Arrays.asList(packages), null /* disallowedApplications */); + assertEquals(rangeSet( + uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]), + uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2])), + allow); + + // Denied list + final Set> disallow = + vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, + null /* allowedApplications */, Arrays.asList(packages)); + assertEquals(rangeSet( + uidRange(userStart, userStart + PKG_UIDS[0] - 1), + uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1), + /* Empty range between UIDS[1] and UIDS[2], should be excluded, */ + uidRange(userStart + PKG_UIDS[2] + 1, userStop)), + disallow); + } + + @Test + public void testGetAlwaysAndOnGetLockDown() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + + // Default state. + assertFalse(vpn.getAlwaysOn()); + assertFalse(vpn.getLockdown()); + + // Set always-on without lockdown. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList())); + assertTrue(vpn.getAlwaysOn()); + assertFalse(vpn.getLockdown()); + + // Set always-on with lockdown. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList())); + assertTrue(vpn.getAlwaysOn()); + assertTrue(vpn.getLockdown()); + + // Remove always-on configuration. + assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList())); + assertFalse(vpn.getAlwaysOn()); + assertFalse(vpn.getLockdown()); + } + + @Test + public void testLockdownChangingPackage() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + final Range user = PRI_USER_RANGE; + final int userStart = user.getLower(); + final int userStop = user.getUpper(); + // Set always-on without lockdown. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null)); + + // Set always-on with lockdown. + assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null)); + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1), + new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop) + })); + + // Switch to another app. + assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1), + new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop) + })); + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1), + new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop) + })); + } + + @Test + public void testLockdownAllowlist() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + final Range user = PRI_USER_RANGE; + final int userStart = user.getLower(); + final int userStop = user.getUpper(); + // Set always-on with lockdown and allow app PKGS[2] from lockdown. + assertTrue(vpn.setAlwaysOnPackage( + PKGS[1], true, Collections.singletonList(PKGS[2]))); + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1), + new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop) + })); + // Change allowed app list to PKGS[3]. + assertTrue(vpn.setAlwaysOnPackage( + PKGS[1], true, Collections.singletonList(PKGS[3]))); + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop) + })); + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1), + new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop) + })); + + // Change the VPN app. + assertTrue(vpn.setAlwaysOnPackage( + PKGS[0], true, Collections.singletonList(PKGS[3]))); + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1), + new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1) + })); + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1), + new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1) + })); + + // Remove the list of allowed packages. + assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null)); + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1), + new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop) + })); + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop), + })); + + // Add the list of allowed packages. + assertTrue(vpn.setAlwaysOnPackage( + PKGS[0], true, Collections.singletonList(PKGS[1]))); + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop) + })); + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1), + new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop) + })); + + // Try allowing a package with a comma, should be rejected. + assertFalse(vpn.setAlwaysOnPackage( + PKGS[0], true, Collections.singletonList("a.b,c.d"))); + + // Pass a non-existent packages in the allowlist, they (and only they) should be ignored. + // allowed package should change from PGKS[1] to PKGS[2]. + assertTrue(vpn.setAlwaysOnPackage( + PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"))); + verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1), + new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop) + })); + verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { + new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1), + new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop) + })); + } + + @Test + public void testLockdownRuleRepeatability() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] { + new UidRangeParcel(PRI_USER_RANGE.getLower(), PRI_USER_RANGE.getUpper())}; + // Given legacy lockdown is already enabled, + vpn.setLockdown(true); + verify(mConnectivityManager, times(1)).setRequireVpnForUids(true, + toRanges(primaryUserRangeParcel)); + + // Enabling legacy lockdown twice should do nothing. + vpn.setLockdown(true); + verify(mConnectivityManager, times(1)).setRequireVpnForUids(anyBoolean(), any()); + + // And disabling should remove the rules exactly once. + vpn.setLockdown(false); + verify(mConnectivityManager, times(1)).setRequireVpnForUids(false, + toRanges(primaryUserRangeParcel)); + + // Removing the lockdown again should have no effect. + vpn.setLockdown(false); + verify(mConnectivityManager, times(2)).setRequireVpnForUids(anyBoolean(), any()); + } + + private ArrayList> toRanges(UidRangeParcel[] ranges) { + ArrayList> rangesArray = new ArrayList<>(ranges.length); + for (int i = 0; i < ranges.length; i++) { + rangesArray.add(new Range<>(ranges[i].start, ranges[i].stop)); + } + return rangesArray; + } + + @Test + public void testLockdownRuleReversibility() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + final UidRangeParcel[] entireUser = { + new UidRangeParcel(PRI_USER_RANGE.getLower(), PRI_USER_RANGE.getUpper()) + }; + final UidRangeParcel[] exceptPkg0 = { + new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1), + new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop) + }; + + final InOrder order = inOrder(mConnectivityManager); + + // Given lockdown is enabled with no package (legacy VPN), + vpn.setLockdown(true); + order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser)); + + // When a new VPN package is set the rules should change to cover that package. + vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE); + order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(entireUser)); + order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(exceptPkg0)); + + // When that VPN package is unset, everything should be undone again in reverse. + vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE); + order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(exceptPkg0)); + order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser)); + } + + @Test + public void testIsAlwaysOnPackageSupported() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + + ApplicationInfo appInfo = new ApplicationInfo(); + when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(primaryUser.id))) + .thenReturn(appInfo); + + ServiceInfo svcInfo = new ServiceInfo(); + ResolveInfo resInfo = new ResolveInfo(); + resInfo.serviceInfo = svcInfo; + when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA), + eq(primaryUser.id))) + .thenReturn(Collections.singletonList(resInfo)); + + // null package name should return false + assertFalse(vpn.isAlwaysOnPackageSupported(null)); + + // Pre-N apps are not supported + appInfo.targetSdkVersion = VERSION_CODES.M; + assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); + + // N+ apps are supported by default + appInfo.targetSdkVersion = VERSION_CODES.N; + assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0])); + + // Apps that opt out explicitly are not supported + appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; + Bundle metaData = new Bundle(); + metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false); + svcInfo.metaData = metaData; + assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); + } + + @Test + public void testNotificationShownForAlwaysOnApp() throws Exception { + final UserHandle userHandle = UserHandle.of(primaryUser.id); + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + final InOrder order = inOrder(mNotificationManager); + + // Don't show a notification for regular disconnected states. + vpn.updateState(DetailedState.DISCONNECTED, TAG); + order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt()); + + // Start showing a notification for disconnected once always-on. + vpn.setAlwaysOnPackage(PKGS[0], false, null); + order.verify(mNotificationManager).notify(anyString(), anyInt(), any()); + + // Stop showing the notification once connected. + vpn.updateState(DetailedState.CONNECTED, TAG); + order.verify(mNotificationManager).cancel(anyString(), anyInt()); + + // Show the notification if we disconnect again. + vpn.updateState(DetailedState.DISCONNECTED, TAG); + order.verify(mNotificationManager).notify(anyString(), anyInt(), any()); + + // Notification should be cleared after unsetting always-on package. + vpn.setAlwaysOnPackage(null, false, null); + order.verify(mNotificationManager).cancel(anyString(), anyInt()); + } + + /** + * The profile name should NOT change between releases for backwards compatibility + * + *

      If this is changed between releases, the {@link Vpn#getVpnProfilePrivileged()} method MUST + * be updated to ensure backward compatibility. + */ + @Test + public void testGetProfileNameForPackage() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + final String expected = Credentials.PLATFORM_VPN + primaryUser.id + "_" + TEST_VPN_PKG; + assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG)); + } + + private Vpn createVpnAndSetupUidChecks(String... grantedOps) throws Exception { + return createVpnAndSetupUidChecks(primaryUser, grantedOps); + } + + private Vpn createVpnAndSetupUidChecks(UserInfo user, String... grantedOps) throws Exception { + final Vpn vpn = createVpn(user.id); + setMockedUsers(user); + + when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) + .thenReturn(Process.myUid()); + + for (final String opStr : grantedOps) { + when(mAppOps.noteOpNoThrow(opStr, Process.myUid(), TEST_VPN_PKG, + null /* attributionTag */, null /* message */)) + .thenReturn(AppOpsManager.MODE_ALLOWED); + } + + return vpn; + } + + private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) { + assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile)); + + // The profile should always be stored, whether or not consent has been previously granted. + verify(mVpnProfileStore) + .put( + eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), + eq(mVpnProfile.encode())); + + for (final String checkedOpStr : checkedOps) { + verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG, + null /* attributionTag */, null /* message */); + } + } + + @Test + public void testProvisionVpnProfileNoIpsecTunnels() throws Exception { + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) + .thenReturn(false); + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + try { + checkProvisionVpnProfile( + vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + fail("Expected exception due to missing feature"); + } catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testProvisionVpnProfilePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + checkProvisionVpnProfile( + vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + } + + @Test + public void testProvisionVpnProfileNotPreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + // Expect that both the ACTIVATE_VPN and ACTIVATE_PLATFORM_VPN were tried, but the caller + // had neither. + checkProvisionVpnProfile(vpn, false /* expectedResult */, + AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, AppOpsManager.OPSTR_ACTIVATE_VPN); + } + + @Test + public void testProvisionVpnProfileVpnServicePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN); + + checkProvisionVpnProfile(vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_VPN); + } + + @Test + public void testProvisionVpnProfileTooLarge() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + final VpnProfile bigProfile = new VpnProfile(""); + bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]); + + try { + vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile); + fail("Expected IAE due to profile size"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testProvisionVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + try { + vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testDeleteVpnProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + vpn.deleteVpnProfile(TEST_VPN_PKG); + + verify(mVpnProfileStore) + .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + } + + @Test + public void testDeleteVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + try { + vpn.deleteVpnProfile(TEST_VPN_PKG); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testGetVpnProfilePrivileged() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(new VpnProfile("").encode()); + + vpn.getVpnProfilePrivileged(TEST_VPN_PKG); + + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + } + + @Test + public void testStartVpnProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + vpn.startVpnProfile(TEST_VPN_PKG); + + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(null) /* attributionTag */, + eq(null) /* message */); + } + + @Test + public void testStartVpnProfileVpnServicePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN); + + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + vpn.startVpnProfile(TEST_VPN_PKG); + + // Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown. + verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(), + TEST_VPN_PKG, null /* attributionTag */, null /* message */); + } + + @Test + public void testStartVpnProfileNotConsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + try { + vpn.startVpnProfile(TEST_VPN_PKG); + fail("Expected failure due to no user consent"); + } catch (SecurityException expected) { + } + + // Verify both appops were checked. + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(null) /* attributionTag */, + eq(null) /* message */); + verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(), + TEST_VPN_PKG, null /* attributionTag */, null /* message */); + + // Keystore should never have been accessed. + verify(mVpnProfileStore, never()).get(any()); + } + + @Test + public void testStartVpnProfileMissingProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null); + + try { + vpn.startVpnProfile(TEST_VPN_PKG); + fail("Expected failure due to missing profile"); + } catch (IllegalArgumentException expected) { + } + + verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(null) /* attributionTag */, + eq(null) /* message */); + } + + @Test + public void testStartVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + try { + vpn.startVpnProfile(TEST_VPN_PKG); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testStopVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + + try { + vpn.stopVpnProfile(TEST_VPN_PKG); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testSetPackageAuthorizationVpnService() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OPSTR_ACTIVATE_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_ALLOWED)); + } + + @Test + public void testSetPackageAuthorizationPlatformVpn() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_PLATFORM)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_ALLOWED)); + } + + @Test + public void testSetPackageAuthorizationRevokeAuthorization() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OPSTR_ACTIVATE_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_IGNORED)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_IGNORED)); + } + + private NetworkCallback triggerOnAvailableAndGetCallback() throws Exception { + final ArgumentCaptor networkCallbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)) + .requestNetwork(any(), networkCallbackCaptor.capture()); + + // onAvailable() will trigger onDefaultNetworkChanged(), so NetdUtils#setInterfaceUp will be + // invoked. Set the return value of INetd#interfaceGetCfg to prevent NullPointerException. + final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel(); + config.flags = new String[] {IF_STATE_DOWN}; + when(mNetd.interfaceGetCfg(anyString())).thenReturn(config); + final NetworkCallback cb = networkCallbackCaptor.getValue(); + cb.onAvailable(TEST_NETWORK); + return cb; + } + + private void verifyInterfaceSetCfgWithFlags(String flag) throws Exception { + // Add a timeout for waiting for interfaceSetCfg to be called. + verify(mNetd, timeout(TEST_TIMEOUT_MS)).interfaceSetCfg(argThat( + config -> Arrays.asList(config.flags).contains(flag))); + } + + @Test + public void testStartPlatformVpnAuthenticationFailed() throws Exception { + final ArgumentCaptor captor = + ArgumentCaptor.forClass(IkeSessionCallback.class); + final IkeProtocolException exception = mock(IkeProtocolException.class); + when(exception.getErrorType()) + .thenReturn(IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED); + + final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), (mVpnProfile)); + final NetworkCallback cb = triggerOnAvailableAndGetCallback(); + + verifyInterfaceSetCfgWithFlags(IF_STATE_UP); + + // Wait for createIkeSession() to be called before proceeding in order to ensure consistent + // state + verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)) + .createIkeSession(any(), any(), any(), any(), captor.capture(), any()); + final IkeSessionCallback ikeCb = captor.getValue(); + ikeCb.onClosedExceptionally(exception); + + verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb)); + assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state); + } + + @Test + public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception { + when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any())) + .thenThrow(new IllegalArgumentException()); + final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), mVpnProfile); + final NetworkCallback cb = triggerOnAvailableAndGetCallback(); + + verifyInterfaceSetCfgWithFlags(IF_STATE_UP); + + // Wait for createIkeSession() to be called before proceeding in order to ensure consistent + // state + verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb)); + assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state); + } + + private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) { + assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null)); + + verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mAppOps).setMode( + eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_ALLOWED)); + + verify(mSystemServices).settingsSecurePutStringForUser( + eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id)); + verify(mSystemServices).settingsSecurePutIntForUser( + eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0), + eq(primaryUser.id)); + verify(mSystemServices).settingsSecurePutStringForUser( + eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id)); + } + + @Test + public void testSetAndStartAlwaysOnVpn() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + // UID checks must return a different UID; otherwise it'll be treated as already prepared. + final int uid = Process.myUid() + 1; + when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) + .thenReturn(uid); + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + setAndVerifyAlwaysOnPackage(vpn, uid, false); + assertTrue(vpn.startAlwaysOnVpn()); + + // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in + // a subsequent CL. + } + + private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception { + setMockedUsers(primaryUser); + + // Dummy egress interface + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(EGRESS_IFACE); + + final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), + InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE); + lp.addRoute(defaultRoute); + + vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp); + return vpn; + } + + @Test + public void testStartPlatformVpn() throws Exception { + startLegacyVpn(createVpn(primaryUser.id), mVpnProfile); + // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in + // a subsequent patch. + } + + @Test + public void testStartRacoonNumericAddress() throws Exception { + startRacoon("1.2.3.4", "1.2.3.4"); + } + + @Test + public void testStartRacoonHostname() throws Exception { + startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve + } + + private void assertTransportInfoMatches(NetworkCapabilities nc, int type) { + assertNotNull(nc); + VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo(); + assertNotNull(ti); + assertEquals(type, ti.getType()); + } + + public void startRacoon(final String serverAddr, final String expectedAddr) + throws Exception { + final ConditionVariable legacyRunnerReady = new ConditionVariable(); + final VpnProfile profile = new VpnProfile("testProfile" /* key */); + profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK; + profile.name = "testProfileName"; + profile.username = "userName"; + profile.password = "thePassword"; + profile.server = serverAddr; + profile.ipsecIdentifier = "id"; + profile.ipsecSecret = "secret"; + profile.l2tpSecret = "l2tpsecret"; + + when(mConnectivityManager.getAllNetworks()) + .thenReturn(new Network[] { new Network(101) }); + + when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(), + any(), any(), anyInt())).thenAnswer(invocation -> { + // The runner has registered an agent and is now ready. + legacyRunnerReady.open(); + return new Network(102); + }); + final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile); + final TestDeps deps = (TestDeps) vpn.mDeps; + try { + // udppsk and 1701 are the values for TYPE_L2TP_IPSEC_PSK + assertArrayEquals( + new String[] { EGRESS_IFACE, expectedAddr, "udppsk", + profile.ipsecIdentifier, profile.ipsecSecret, "1701" }, + deps.racoonArgs.get(10, TimeUnit.SECONDS)); + // literal values are hardcoded in Vpn.java for mtpd args + assertArrayEquals( + new String[] { EGRESS_IFACE, "l2tp", expectedAddr, "1701", profile.l2tpSecret, + "name", profile.username, "password", profile.password, + "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns", + "idle", "1800", "mtu", "1270", "mru", "1270" }, + deps.mtpdArgs.get(10, TimeUnit.SECONDS)); + + // Now wait for the runner to be ready before testing for the route. + ArgumentCaptor lpCaptor = ArgumentCaptor.forClass(LinkProperties.class); + ArgumentCaptor ncCaptor = + ArgumentCaptor.forClass(NetworkCapabilities.class); + verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(), + lpCaptor.capture(), ncCaptor.capture(), any(), any(), anyInt()); + + // In this test the expected address is always v4 so /32. + // Note that the interface needs to be specified because RouteInfo objects stored in + // LinkProperties objects always acquire the LinkProperties' interface. + final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"), + null, EGRESS_IFACE, RouteInfo.RTN_THROW); + final List actualRoutes = lpCaptor.getValue().getRoutes(); + assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes, + actualRoutes.contains(expectedRoute)); + + assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY); + } finally { + // Now interrupt the thread, unblock the runner and clean up. + vpn.mVpnRunner.exitVpnRunner(); + deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier + vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup + } + } + + private static final class TestDeps extends Vpn.Dependencies { + public final CompletableFuture racoonArgs = new CompletableFuture(); + public final CompletableFuture mtpdArgs = new CompletableFuture(); + public final File mStateFile; + + private final HashMap mRunningServices = new HashMap<>(); + + TestDeps() { + try { + mStateFile = File.createTempFile("vpnTest", ".tmp"); + mStateFile.deleteOnExit(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isCallerSystem() { + return true; + } + + @Override + public void startService(final String serviceName) { + mRunningServices.put(serviceName, true); + } + + @Override + public void stopService(final String serviceName) { + mRunningServices.put(serviceName, false); + } + + @Override + public boolean isServiceRunning(final String serviceName) { + return mRunningServices.getOrDefault(serviceName, false); + } + + @Override + public boolean isServiceStopped(final String serviceName) { + return !isServiceRunning(serviceName); + } + + @Override + public File getStateFile() { + return mStateFile; + } + + @Override + public PendingIntent getIntentForStatusPanel(Context context) { + return null; + } + + @Override + public void sendArgumentsToDaemon( + final String daemon, final LocalSocket socket, final String[] arguments, + final Vpn.RetryScheduler interruptChecker) throws IOException { + if ("racoon".equals(daemon)) { + racoonArgs.complete(arguments); + } else if ("mtpd".equals(daemon)) { + writeStateFile(arguments); + mtpdArgs.complete(arguments); + } else { + throw new UnsupportedOperationException("Unsupported daemon : " + daemon); + } + } + + private void writeStateFile(final String[] arguments) throws IOException { + mStateFile.delete(); + mStateFile.createNewFile(); + mStateFile.deleteOnExit(); + final BufferedWriter writer = new BufferedWriter( + new FileWriter(mStateFile, false /* append */)); + writer.write(EGRESS_IFACE); + writer.write("\n"); + // addresses + writer.write("10.0.0.1/24\n"); + // routes + writer.write("192.168.6.0/24\n"); + // dns servers + writer.write("192.168.6.1\n"); + // search domains + writer.write("vpn.searchdomains.com\n"); + // endpoint - intentionally empty + writer.write("\n"); + writer.flush(); + writer.close(); + } + + @Override + @NonNull + public InetAddress resolve(final String endpoint) { + try { + // If a numeric IP address, return it. + return InetAddress.parseNumericAddress(endpoint); + } catch (IllegalArgumentException e) { + // Otherwise, return some token IP to test for. + return InetAddress.parseNumericAddress("5.6.7.8"); + } + } + + @Override + public boolean isInterfacePresent(final Vpn vpn, final String iface) { + return true; + } + } + + /** + * Mock some methods of vpn object. + */ + private Vpn createVpn(@UserIdInt int userId) { + final Context asUserContext = mock(Context.class, AdditionalAnswers.delegatesTo(mContext)); + doReturn(UserHandle.of(userId)).when(asUserContext).getUser(); + when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt())) + .thenReturn(asUserContext); + final TestLooper testLooper = new TestLooper(); + final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService, + mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator); + verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat( + provider -> provider.getName().contains("VpnNetworkProvider") + )); + return vpn; + } + + /** + * Populate {@link #mUserManager} with a list of fake users. + */ + private void setMockedUsers(UserInfo... users) { + final Map userMap = new ArrayMap<>(); + for (UserInfo user : users) { + userMap.put(user.id, user); + } + + /** + * @see UserManagerService#getUsers(boolean) + */ + doAnswer(invocation -> { + final ArrayList result = new ArrayList<>(users.length); + for (UserInfo ui : users) { + if (ui.isEnabled() && !ui.partial) { + result.add(ui); + } + } + return result; + }).when(mUserManager).getAliveUsers(); + + doAnswer(invocation -> { + final int id = (int) invocation.getArguments()[0]; + return userMap.get(id); + }).when(mUserManager).getUserInfo(anyInt()); + } + + /** + * Populate {@link #mPackageManager} with a fake packageName-to-UID mapping. + */ + private void setMockedPackages(final Map packages) { + try { + doAnswer(invocation -> { + final String appName = (String) invocation.getArguments()[0]; + final int userId = (int) invocation.getArguments()[1]; + Integer appId = packages.get(appName); + if (appId == null) throw new PackageManager.NameNotFoundException(appName); + return UserHandle.getUid(userId, appId); + }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt()); + } catch (Exception e) { + } + } + + private void setMockedNetworks(final Map networks) { + doAnswer(invocation -> { + final Network network = (Network) invocation.getArguments()[0]; + return networks.get(network); + }).when(mConnectivityManager).getNetworkCapabilities(any()); + } + + // Need multiple copies of this, but Java's Stream objects can't be reused or + // duplicated. + private Stream publicIpV4Routes() { + return Stream.of( + "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", + "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", + "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", + "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", + "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14", + "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7", + "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"); + } + + private Stream publicIpV6Routes() { + return Stream.of( + "::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6", + "fe00::/8", "2605:ef80:e:af1d::/64"); + } +} diff --git a/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java new file mode 100644 index 0000000000..8b730af769 --- /dev/null +++ b/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.net; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.Manifest.permission; +import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManagerInternal; +import android.content.Context; +import android.content.pm.PackageManager; +import android.telephony.TelephonyManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsAccessTest { + private static final String TEST_PKG = "com.example.test"; + private static final int TEST_UID = 12345; + + @Mock private Context mContext; + @Mock private DevicePolicyManagerInternal mDpmi; + @Mock private TelephonyManager mTm; + @Mock private AppOpsManager mAppOps; + + // Hold the real service so we can restore it when tearing down the test. + private DevicePolicyManagerInternal mSystemDpmi; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class); + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.addService(DevicePolicyManagerInternal.class, mDpmi); + + when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTm); + when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps); + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi); + } + + @Test + public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception { + setHasCarrierPrivileges(true); + setIsDeviceOwner(false); + setIsProfileOwner(false); + setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false); + setHasReadHistoryPermission(false); + assertEquals(NetworkStatsAccess.Level.DEVICE, + NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG)); + } + + @Test + public void testCheckAccessLevel_isDeviceOwner() throws Exception { + setHasCarrierPrivileges(false); + setIsDeviceOwner(true); + setIsProfileOwner(false); + setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false); + setHasReadHistoryPermission(false); + assertEquals(NetworkStatsAccess.Level.DEVICE, + NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG)); + } + + @Test + public void testCheckAccessLevel_isProfileOwner() throws Exception { + setHasCarrierPrivileges(false); + setIsDeviceOwner(false); + setIsProfileOwner(true); + setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false); + setHasReadHistoryPermission(false); + assertEquals(NetworkStatsAccess.Level.USER, + NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG)); + } + + @Test + public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception { + setHasCarrierPrivileges(false); + setIsDeviceOwner(false); + setIsProfileOwner(true); + setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false); + setHasReadHistoryPermission(false); + assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY, + NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG)); + } + + @Test + public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception { + setHasCarrierPrivileges(false); + setIsDeviceOwner(false); + setIsProfileOwner(true); + setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true); + setHasReadHistoryPermission(false); + assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY, + NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG)); + } + + @Test + public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception { + setHasCarrierPrivileges(false); + setIsDeviceOwner(false); + setIsProfileOwner(true); + setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false); + setHasReadHistoryPermission(true); + assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY, + NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG)); + } + + @Test + public void testCheckAccessLevel_deniedAppOpsBit() throws Exception { + setHasCarrierPrivileges(false); + setIsDeviceOwner(false); + setIsProfileOwner(false); + setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true); + setHasReadHistoryPermission(false); + assertEquals(NetworkStatsAccess.Level.DEFAULT, + NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG)); + } + + @Test + public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception { + setHasCarrierPrivileges(false); + setIsDeviceOwner(false); + setIsProfileOwner(false); + setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false); + setHasReadHistoryPermission(false); + assertEquals(NetworkStatsAccess.Level.DEFAULT, + NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG)); + } + + private void setHasCarrierPrivileges(boolean hasPrivileges) { + when(mTm.checkCarrierPrivilegesForPackageAnyPhone(TEST_PKG)).thenReturn( + hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS + : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS); + } + + private void setIsDeviceOwner(boolean isOwner) { + when(mDpmi.isActiveDeviceOwner(TEST_UID)).thenReturn(isOwner); + } + + private void setIsProfileOwner(boolean isOwner) { + when(mDpmi.isActiveProfileOwner(TEST_UID)).thenReturn(isOwner); + } + + private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) { + when(mAppOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG)) + .thenReturn(appOpsMode); + when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn( + hasPermission ? PackageManager.PERMISSION_GRANTED + : PackageManager.PERMISSION_DENIED); + } + + private void setHasReadHistoryPermission(boolean hasPermission) { + when(mContext.checkCallingOrSelfPermission(permission.READ_NETWORK_USAGE_HISTORY)) + .thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED + : PackageManager.PERMISSION_DENIED); + } +} diff --git a/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java new file mode 100644 index 0000000000..a058a466a4 --- /dev/null +++ b/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkStats.ROAMING_ALL; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.ROAMING_YES; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.SET_FOREGROUND; +import static android.net.NetworkStats.TAG_NONE; + +import static org.junit.Assert.assertEquals; + +import android.net.NetworkStats; +import android.net.UnderlyingNetworkInfo; + +import java.util.Arrays; + +/** Superclass with utilities for NetworkStats(Service|Factory)Test */ +abstract class NetworkStatsBaseTest { + static final String TEST_IFACE = "test0"; + static final String TEST_IFACE2 = "test1"; + static final String TUN_IFACE = "test_nss_tun0"; + static final String TUN_IFACE2 = "test_nss_tun1"; + + static final int UID_RED = 1001; + static final int UID_BLUE = 1002; + static final int UID_GREEN = 1003; + static final int UID_VPN = 1004; + + void assertValues(NetworkStats stats, String iface, int uid, long rxBytes, + long rxPackets, long txBytes, long txPackets) { + assertValues( + stats, iface, uid, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, + rxBytes, rxPackets, txBytes, txPackets, 0); + } + + void assertValues(NetworkStats stats, String iface, int uid, int set, int tag, + int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, + long txBytes, long txPackets, long operations) { + final NetworkStats.Entry entry = new NetworkStats.Entry(); + final int[] sets; + if (set == SET_ALL) { + sets = new int[] {SET_ALL, SET_DEFAULT, SET_FOREGROUND}; + } else { + sets = new int[] {set}; + } + + final int[] roamings; + if (roaming == ROAMING_ALL) { + roamings = new int[] {ROAMING_ALL, ROAMING_YES, ROAMING_NO}; + } else { + roamings = new int[] {roaming}; + } + + final int[] meterings; + if (metered == METERED_ALL) { + meterings = new int[] {METERED_ALL, METERED_YES, METERED_NO}; + } else { + meterings = new int[] {metered}; + } + + final int[] defaultNetworks; + if (defaultNetwork == DEFAULT_NETWORK_ALL) { + defaultNetworks = + new int[] {DEFAULT_NETWORK_ALL, DEFAULT_NETWORK_YES, DEFAULT_NETWORK_NO}; + } else { + defaultNetworks = new int[] {defaultNetwork}; + } + + for (int s : sets) { + for (int r : roamings) { + for (int m : meterings) { + for (int d : defaultNetworks) { + final int i = stats.findIndex(iface, uid, s, tag, m, r, d); + if (i != -1) { + entry.add(stats.getValues(i, null)); + } + } + } + } + } + + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + assertEquals("unexpected txPackets", txPackets, entry.txPackets); + assertEquals("unexpected operations", operations, entry.operations); + } + + static UnderlyingNetworkInfo createVpnInfo(String[] underlyingIfaces) { + return createVpnInfo(TUN_IFACE, underlyingIfaces); + } + + static UnderlyingNetworkInfo createVpnInfo(String vpnIface, String[] underlyingIfaces) { + return new UnderlyingNetworkInfo(UID_VPN, vpnIface, Arrays.asList(underlyingIfaces)); + } +} diff --git a/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java new file mode 100644 index 0000000000..505ff9b6a3 --- /dev/null +++ b/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.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 com.android.server.net; + +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.NetworkIdentity.OEM_NONE; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStatsHistory.FIELD_ALL; +import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.os.Process.myUid; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; + +import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.NetworkIdentity; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.os.Process; +import android.os.UserHandle; +import android.telephony.SubscriptionPlan; +import android.telephony.TelephonyManager; +import android.text.format.DateUtils; +import android.util.RecurrenceRule; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.tests.net.R; + +import libcore.io.IoUtils; +import libcore.io.Streams; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link NetworkStatsCollection}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsCollectionTest { + + private static final String TEST_FILE = "test.bin"; + private static final String TEST_IMSI = "310260000000000"; + + private static final long TIME_A = 1326088800000L; // UTC: Monday 9th January 2012 06:00:00 AM + private static final long TIME_B = 1326110400000L; // UTC: Monday 9th January 2012 12:00:00 PM + private static final long TIME_C = 1326132000000L; // UTC: Monday 9th January 2012 06:00:00 PM + + private static Clock sOriginalClock; + + @Before + public void setUp() throws Exception { + sOriginalClock = RecurrenceRule.sClock; + // ignore any device overlay while testing + NetworkTemplate.forceAllNetworkTypes(); + } + + @After + public void tearDown() throws Exception { + RecurrenceRule.sClock = sOriginalClock; + NetworkTemplate.resetForceAllNetworkTypes(); + } + + private void setClock(Instant instant) { + RecurrenceRule.sClock = Clock.fixed(instant, ZoneId.systemDefault()); + } + + @Test + public void testReadLegacyNetwork() throws Exception { + final File testFile = + new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE); + stageFile(R.raw.netstats_v1, testFile); + + final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + collection.readLegacyNetwork(testFile); + + // verify that history read correctly + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE); + + // now export into a unified format + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + collection.write(bos); + + // clear structure completely + collection.reset(); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE); + + // and read back into structure, verifying that totals are same + collection.read(new ByteArrayInputStream(bos.toByteArray())); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE); + } + + @Test + public void testReadLegacyUid() throws Exception { + final File testFile = + new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE); + stageFile(R.raw.netstats_uid_v4, testFile); + + final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + collection.readLegacyUid(testFile, false); + + // verify that history read correctly + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE); + + // now export into a unified format + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + collection.write(bos); + + // clear structure completely + collection.reset(); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE); + + // and read back into structure, verifying that totals are same + collection.read(new ByteArrayInputStream(bos.toByteArray())); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE); + } + + @Test + public void testReadLegacyUidTags() throws Exception { + final File testFile = + new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE); + stageFile(R.raw.netstats_uid_v4, testFile); + + final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + collection.readLegacyUid(testFile, true); + + // verify that history read correctly + assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI), + 77017831L, 100995L, 35436758L, 92344L); + + // now export into a unified format + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + collection.write(bos); + + // clear structure completely + collection.reset(); + assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI), + 0L, 0L, 0L, 0L); + + // and read back into structure, verifying that totals are same + collection.read(new ByteArrayInputStream(bos.toByteArray())); + assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI), + 77017831L, 100995L, 35436758L, 92344L); + } + + @Test + public void testStartEndAtomicBuckets() throws Exception { + final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS); + + // record empty data straddling between buckets + final NetworkStats.Entry entry = new NetworkStats.Entry(); + entry.rxBytes = 32; + collection.recordData(null, UID_ALL, SET_DEFAULT, TAG_NONE, 30 * MINUTE_IN_MILLIS, + 90 * MINUTE_IN_MILLIS, entry); + + // assert that we report boundary in atomic buckets + assertEquals(0, collection.getStartMillis()); + assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis()); + } + + @Test + public void testAccessLevels() throws Exception { + final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + final NetworkIdentitySet identSet = new NetworkIdentitySet(); + identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, + TEST_IMSI, null, false, true, true, OEM_NONE)); + + int myUid = Process.myUid(); + int otherUidInSameUser = Process.myUid() + 1; + int uidInDifferentUser = Process.myUid() + UserHandle.PER_USER_RANGE; + + // Record one entry for the current UID. + entry.rxBytes = 32; + collection.recordData(identSet, myUid, SET_DEFAULT, TAG_NONE, 0, 60 * MINUTE_IN_MILLIS, + entry); + + // Record one entry for another UID in this user. + entry.rxBytes = 64; + collection.recordData(identSet, otherUidInSameUser, SET_DEFAULT, TAG_NONE, 0, + 60 * MINUTE_IN_MILLIS, entry); + + // Record one entry for the system UID. + entry.rxBytes = 128; + collection.recordData(identSet, Process.SYSTEM_UID, SET_DEFAULT, TAG_NONE, 0, + 60 * MINUTE_IN_MILLIS, entry); + + // Record one entry for a UID in a different user. + entry.rxBytes = 256; + collection.recordData(identSet, uidInDifferentUser, SET_DEFAULT, TAG_NONE, 0, + 60 * MINUTE_IN_MILLIS, entry); + + // Verify the set of relevant UIDs for each access level. + assertArrayEquals(new int[] { myUid }, + collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT)); + assertArrayEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser }, + collection.getRelevantUids(NetworkStatsAccess.Level.USER)); + assertArrayEquals( + new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser }, + collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE)); + + // Verify security check in getHistory. + assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, myUid, SET_DEFAULT, + TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid)); + try { + collection.getHistory(buildTemplateMobileAll(TEST_IMSI), null, otherUidInSameUser, + SET_DEFAULT, TAG_NONE, 0, 0L, 0L, NetworkStatsAccess.Level.DEFAULT, myUid); + fail("Should have thrown SecurityException for accessing different UID"); + } catch (SecurityException e) { + // expected + } + + // Verify appropriate aggregation in getSummary. + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32, 0, 0, 0, + NetworkStatsAccess.Level.DEFAULT); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128, 0, 0, 0, + NetworkStatsAccess.Level.USER); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128 + 256, 0, 0, + 0, NetworkStatsAccess.Level.DEVICE); + } + + @Test + public void testAugmentPlan() throws Exception { + final File testFile = + new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE); + stageFile(R.raw.netstats_v1, testFile); + + final NetworkStatsCollection emptyCollection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + collection.readLegacyNetwork(testFile); + + // We're in the future, but not that far off + setClock(Instant.parse("2012-06-01T00:00:00.00Z")); + + // Test a bunch of plans that should result in no augmentation + final List plans = new ArrayList<>(); + + // No plan + plans.add(null); + // No usage anchor + plans.add(SubscriptionPlan.Builder + .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z")).build()); + // Usage anchor far in past + plans.add(SubscriptionPlan.Builder + .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z")) + .setDataUsage(1000L, TIME_A - DateUtils.YEAR_IN_MILLIS).build()); + // Usage anchor far in future + plans.add(SubscriptionPlan.Builder + .createRecurringMonthly(ZonedDateTime.parse("2011-01-14T00:00:00.00Z")) + .setDataUsage(1000L, TIME_A + DateUtils.YEAR_IN_MILLIS).build()); + // Usage anchor near but outside cycle + plans.add(SubscriptionPlan.Builder + .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"), + ZonedDateTime.parse("2012-01-09T15:00:00.00Z")) + .setDataUsage(1000L, TIME_C).build()); + + for (SubscriptionPlan plan : plans) { + int i; + NetworkStatsHistory history; + + // Empty collection should be untouched + history = getHistory(emptyCollection, plan, TIME_A, TIME_C); + assertEquals(0L, history.getTotalBytes()); + + // Normal collection should be untouched + history = getHistory(collection, plan, TIME_A, TIME_C); i = 0; + assertEntry(100647, 197, 23649, 185, history.getValues(i++, null)); + assertEntry(100647, 196, 23648, 185, history.getValues(i++, null)); + assertEntry(18323, 76, 15032, 76, history.getValues(i++, null)); + assertEntry(18322, 75, 15031, 75, history.getValues(i++, null)); + assertEntry(527798, 761, 78570, 652, history.getValues(i++, null)); + assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); + assertEntry(10747, 50, 16839, 55, history.getValues(i++, null)); + assertEntry(10747, 49, 16837, 54, history.getValues(i++, null)); + assertEntry(89191, 151, 18021, 140, history.getValues(i++, null)); + assertEntry(89190, 150, 18020, 139, history.getValues(i++, null)); + assertEntry(3821, 23, 4525, 26, history.getValues(i++, null)); + assertEntry(3820, 21, 4524, 26, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEntry(8289, 36, 6864, 39, history.getValues(i++, null)); + assertEntry(8289, 34, 6862, 37, history.getValues(i++, null)); + assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); + assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); + assertEntry(11378, 49, 9261, 50, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 48, history.getValues(i++, null)); + assertEntry(201766, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201764, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 219, 39918, 202, history.getValues(i++, null)); + assertEntry(106105, 216, 39916, 200, history.getValues(i++, null)); + assertEquals(history.size(), i); + + // Slice from middle should be untouched + history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS, + TIME_B + HOUR_IN_MILLIS); i = 0; + assertEntry(3821, 23, 4525, 26, history.getValues(i++, null)); + assertEntry(3820, 21, 4524, 26, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEquals(history.size(), i); + } + + // Lower anchor in the middle of plan + { + int i; + NetworkStatsHistory history; + + final SubscriptionPlan plan = SubscriptionPlan.Builder + .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"), + ZonedDateTime.parse("2012-01-09T15:00:00.00Z")) + .setDataUsage(200000L, TIME_B).build(); + + // Empty collection should be augmented + history = getHistory(emptyCollection, plan, TIME_A, TIME_C); + assertEquals(200000L, history.getTotalBytes()); + + // Normal collection should be augmented + history = getHistory(collection, plan, TIME_A, TIME_C); i = 0; + assertEntry(100647, 197, 23649, 185, history.getValues(i++, null)); + assertEntry(100647, 196, 23648, 185, history.getValues(i++, null)); + assertEntry(18323, 76, 15032, 76, history.getValues(i++, null)); + assertEntry(18322, 75, 15031, 75, history.getValues(i++, null)); + assertEntry(527798, 761, 78570, 652, history.getValues(i++, null)); + assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); + // Cycle point; start data normalization + assertEntry(7507, 0, 11763, 0, history.getValues(i++, null)); + assertEntry(7507, 0, 11762, 0, history.getValues(i++, null)); + assertEntry(62309, 0, 12589, 0, history.getValues(i++, null)); + assertEntry(62309, 0, 12588, 0, history.getValues(i++, null)); + assertEntry(2669, 0, 3161, 0, history.getValues(i++, null)); + assertEntry(2668, 0, 3160, 0, history.getValues(i++, null)); + // Anchor point; end data normalization + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEntry(8289, 36, 6864, 39, history.getValues(i++, null)); + assertEntry(8289, 34, 6862, 37, history.getValues(i++, null)); + assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); + assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); + // Cycle point + assertEntry(11378, 49, 9261, 50, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 48, history.getValues(i++, null)); + assertEntry(201766, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201764, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 219, 39918, 202, history.getValues(i++, null)); + assertEntry(106105, 216, 39916, 200, history.getValues(i++, null)); + assertEquals(history.size(), i); + + // Slice from middle should be augmented + history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS, + TIME_B + HOUR_IN_MILLIS); i = 0; + assertEntry(2669, 0, 3161, 0, history.getValues(i++, null)); + assertEntry(2668, 0, 3160, 0, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEquals(history.size(), i); + } + + // Higher anchor in the middle of plan + { + int i; + NetworkStatsHistory history; + + final SubscriptionPlan plan = SubscriptionPlan.Builder + .createNonrecurring(ZonedDateTime.parse("2012-01-09T09:00:00.00Z"), + ZonedDateTime.parse("2012-01-09T15:00:00.00Z")) + .setDataUsage(400000L, TIME_B + MINUTE_IN_MILLIS).build(); + + // Empty collection should be augmented + history = getHistory(emptyCollection, plan, TIME_A, TIME_C); + assertEquals(400000L, history.getTotalBytes()); + + // Normal collection should be augmented + history = getHistory(collection, plan, TIME_A, TIME_C); i = 0; + assertEntry(100647, 197, 23649, 185, history.getValues(i++, null)); + assertEntry(100647, 196, 23648, 185, history.getValues(i++, null)); + assertEntry(18323, 76, 15032, 76, history.getValues(i++, null)); + assertEntry(18322, 75, 15031, 75, history.getValues(i++, null)); + assertEntry(527798, 761, 78570, 652, history.getValues(i++, null)); + assertEntry(527797, 760, 78570, 651, history.getValues(i++, null)); + // Cycle point; start data normalization + assertEntry(15015, 0, 23527, 0, history.getValues(i++, null)); + assertEntry(15015, 0, 23524, 0, history.getValues(i++, null)); + assertEntry(124619, 0, 25179, 0, history.getValues(i++, null)); + assertEntry(124618, 0, 25177, 0, history.getValues(i++, null)); + assertEntry(5338, 0, 6322, 0, history.getValues(i++, null)); + assertEntry(5337, 0, 6320, 0, history.getValues(i++, null)); + // Anchor point; end data normalization + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEntry(8289, 36, 6864, 39, history.getValues(i++, null)); + assertEntry(8289, 34, 6862, 37, history.getValues(i++, null)); + assertEntry(113914, 174, 18364, 157, history.getValues(i++, null)); + assertEntry(113913, 173, 18364, 157, history.getValues(i++, null)); + // Cycle point + assertEntry(11378, 49, 9261, 50, history.getValues(i++, null)); + assertEntry(11377, 48, 9261, 48, history.getValues(i++, null)); + assertEntry(201766, 328, 41808, 291, history.getValues(i++, null)); + assertEntry(201764, 328, 41807, 290, history.getValues(i++, null)); + assertEntry(106106, 219, 39918, 202, history.getValues(i++, null)); + assertEntry(106105, 216, 39916, 200, history.getValues(i++, null)); + + // Slice from middle should be augmented + history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS, + TIME_B + HOUR_IN_MILLIS); i = 0; + assertEntry(5338, 0, 6322, 0, history.getValues(i++, null)); + assertEntry(5337, 0, 6320, 0, history.getValues(i++, null)); + assertEntry(91686, 159, 18576, 146, history.getValues(i++, null)); + assertEntry(91685, 159, 18574, 146, history.getValues(i++, null)); + assertEquals(history.size(), i); + } + } + + @Test + public void testAugmentPlanGigantic() throws Exception { + // We're in the future, but not that far off + setClock(Instant.parse("2012-06-01T00:00:00.00Z")); + + // Create a simple history with a ton of measured usage + final NetworkStatsCollection large = new NetworkStatsCollection(HOUR_IN_MILLIS); + final NetworkIdentitySet ident = new NetworkIdentitySet(); + ident.add(new NetworkIdentity(ConnectivityManager.TYPE_MOBILE, -1, TEST_IMSI, null, + false, true, true, OEM_NONE)); + large.recordData(ident, UID_ALL, SET_ALL, TAG_NONE, TIME_A, TIME_B, + new NetworkStats.Entry(12_730_893_164L, 1, 0, 0, 0)); + + // Verify untouched total + assertEquals(12_730_893_164L, getHistory(large, null, TIME_A, TIME_C).getTotalBytes()); + + // Verify anchor that might cause overflows + final SubscriptionPlan plan = SubscriptionPlan.Builder + .createRecurringMonthly(ZonedDateTime.parse("2012-01-09T00:00:00.00Z")) + .setDataUsage(4_939_212_390L, TIME_B).build(); + assertEquals(4_939_212_386L, getHistory(large, plan, TIME_A, TIME_C).getTotalBytes()); + } + + @Test + public void testRounding() throws Exception { + final NetworkStatsCollection coll = new NetworkStatsCollection(HOUR_IN_MILLIS); + + // Special values should remain unchanged + for (long time : new long[] { + Long.MIN_VALUE, Long.MAX_VALUE, SubscriptionPlan.TIME_UNKNOWN + }) { + assertEquals(time, coll.roundUp(time)); + assertEquals(time, coll.roundDown(time)); + } + + assertEquals(TIME_A, coll.roundUp(TIME_A)); + assertEquals(TIME_A, coll.roundDown(TIME_A)); + + assertEquals(TIME_A + HOUR_IN_MILLIS, coll.roundUp(TIME_A + 1)); + assertEquals(TIME_A, coll.roundDown(TIME_A + 1)); + + assertEquals(TIME_A, coll.roundUp(TIME_A - 1)); + assertEquals(TIME_A - HOUR_IN_MILLIS, coll.roundDown(TIME_A - 1)); + } + + @Test + public void testMultiplySafeRational() { + assertEquals(25, multiplySafeByRational(50, 1, 2)); + assertEquals(100, multiplySafeByRational(50, 2, 1)); + + assertEquals(-10, multiplySafeByRational(30, -1, 3)); + assertEquals(0, multiplySafeByRational(30, 0, 3)); + assertEquals(10, multiplySafeByRational(30, 1, 3)); + assertEquals(20, multiplySafeByRational(30, 2, 3)); + assertEquals(30, multiplySafeByRational(30, 3, 3)); + assertEquals(40, multiplySafeByRational(30, 4, 3)); + + assertEquals(100_000_000_000L, + multiplySafeByRational(300_000_000_000L, 10_000_000_000L, 30_000_000_000L)); + assertEquals(100_000_000_010L, + multiplySafeByRational(300_000_000_000L, 10_000_000_001L, 30_000_000_000L)); + assertEquals(823_202_048L, + multiplySafeByRational(4_939_212_288L, 2_121_815_528L, 12_730_893_165L)); + + assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0)); + } + + /** + * Copy a {@link Resources#openRawResource(int)} into {@link File} for + * testing purposes. + */ + private void stageFile(int rawId, File file) throws Exception { + new File(file.getParent()).mkdirs(); + InputStream in = null; + OutputStream out = null; + try { + in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId); + out = new FileOutputStream(file); + Streams.copy(in, out); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + } + } + + private static NetworkStatsHistory getHistory(NetworkStatsCollection collection, + SubscriptionPlan augmentPlan, long start, long end) { + return collection.getHistory(buildTemplateMobileAll(TEST_IMSI), augmentPlan, UID_ALL, + SET_ALL, TAG_NONE, FIELD_ALL, start, end, NetworkStatsAccess.Level.DEVICE, myUid()); + } + + private static void assertSummaryTotal(NetworkStatsCollection collection, + NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets, + @NetworkStatsAccess.Level int accessLevel) { + final NetworkStats.Entry actual = collection.getSummary( + template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel, myUid()) + .getTotal(null); + assertEntry(rxBytes, rxPackets, txBytes, txPackets, actual); + } + + private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection, + NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) { + final NetworkStats.Entry actual = collection.getSummary( + template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE, myUid()) + .getTotalIncludingTags(null); + assertEntry(rxBytes, rxPackets, txBytes, txPackets, actual); + } + + private static void assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets, + NetworkStats.Entry actual) { + assertEntry(new NetworkStats.Entry(rxBytes, rxPackets, txBytes, txPackets, 0L), actual); + } + + private static void assertEntry(long rxBytes, long rxPackets, long txBytes, long txPackets, + NetworkStatsHistory.Entry actual) { + assertEntry(new NetworkStats.Entry(rxBytes, rxPackets, txBytes, txPackets, 0L), actual); + } + + private static void assertEntry(NetworkStats.Entry expected, + NetworkStatsHistory.Entry actual) { + assertEntry(expected, new NetworkStats.Entry(actual.rxBytes, actual.rxPackets, + actual.txBytes, actual.txPackets, 0L)); + } + + private static void assertEntry(NetworkStats.Entry expected, + NetworkStats.Entry actual) { + assertEquals("unexpected rxBytes", expected.rxBytes, actual.rxBytes); + assertEquals("unexpected rxPackets", expected.rxPackets, actual.rxPackets); + assertEquals("unexpected txBytes", expected.txBytes, actual.txBytes); + assertEquals("unexpected txPackets", expected.txPackets, actual.txPackets); + } +} diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java new file mode 100644 index 0000000000..f3ae9b051e --- /dev/null +++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_ALL; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.SET_FOREGROUND; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; + +import static com.android.server.NetworkManagementSocketTagger.kernelToTag; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.content.res.Resources; +import android.net.NetworkStats; +import android.net.TrafficStats; +import android.net.UnderlyingNetworkInfo; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.tests.net.R; + +import libcore.io.IoUtils; +import libcore.io.Streams; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.InputStream; +import java.io.OutputStream; + +/** Tests for {@link NetworkStatsFactory}. */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { + private static final String CLAT_PREFIX = "v4-"; + + private File mTestProc; + private NetworkStatsFactory mFactory; + + @Before + public void setUp() throws Exception { + mTestProc = new File(InstrumentationRegistry.getContext().getFilesDir(), "proc"); + if (mTestProc.exists()) { + IoUtils.deleteContents(mTestProc); + } + + // The libandroid_servers which have the native method is not available to + // applications. So in order to have a test support native library, the native code + // related to networkStatsFactory is compiled to a minimal native library and loaded here. + System.loadLibrary("networkstatsfactorytestjni"); + mFactory = new NetworkStatsFactory(mTestProc, false); + mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]); + } + + @After + public void tearDown() throws Exception { + mFactory = null; + + if (mTestProc.exists()) { + IoUtils.deleteContents(mTestProc); + } + } + + @Test + public void testNetworkStatsDetail() throws Exception { + final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical); + + assertEquals(70, stats.size()); + assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L); + assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L); + assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L); + assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L); + assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L); + } + + @Test + public void testVpnRewriteTrafficThroughItself() throws Exception { + UnderlyingNetworkInfo[] underlyingNetworkInfos = + new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN. + // + // VPN UID rewrites packets read from TUN back to TUN, plus some of its own traffic + + final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_rewrite_through_self); + + assertValues(tunStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 2000L, 200L, 1000L, 100L, 0); + assertValues(tunStats, TUN_IFACE, UID_BLUE, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 1000L, 100L, 500L, 50L, 0); + assertValues(tunStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 0L, 0L, 1600L, 160L, 0); + + assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L); + assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 260L, 26L); + } + + @Test + public void testVpnWithClat() throws Exception { + final UnderlyingNetworkInfo[] underlyingNetworkInfos = new UnderlyingNetworkInfo[] { + createVpnInfo(new String[] {CLAT_PREFIX + TEST_IFACE})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + mFactory.noteStackedIface(CLAT_PREFIX + TEST_IFACE, TEST_IFACE); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN. + // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over v4-WiFi, and clat + // added 20 bytes per packet of extra overhead + // + // For 1650 bytes sent over v4-WiFi, 4650 bytes were actually sent over WiFi, which is + // expected to be split as follows: + // UID_RED: 1000 bytes, 100 packets + // UID_BLUE: 500 bytes, 50 packets + // UID_VPN: 3150 bytes, 0 packets + // + // For 3300 bytes received over v4-WiFi, 9300 bytes were actually sent over WiFi, which is + // expected to be split as follows: + // UID_RED: 2000 bytes, 200 packets + // UID_BLUE: 1000 bytes, 100 packets + // UID_VPN: 6300 bytes, 0 packets + final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_with_clat); + + assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_RED, 2000L, 200L, 1000, 100L); + assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L); + assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_VPN, 6300L, 0L, 3150L, 0L); + } + + @Test + public void testVpnWithOneUnderlyingIface() throws Exception { + final UnderlyingNetworkInfo[] underlyingNetworkInfos = + new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN. + // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over WiFi. + // Of 1650 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes + // attributed to UID_BLUE, and 150 bytes attributed to UID_VPN. + // Of 3300 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes + // attributed to UID_BLUE, and 300 bytes attributed to UID_VPN. + final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying); + + assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L); + assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 150L, 0L); + } + + @Test + public void testVpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception { + // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). + final UnderlyingNetworkInfo[] underlyingNetworkInfos = + new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN. + // Additionally, the VPN sends 6000 bytes (600 packets) of its own traffic into the tun + // interface (passing that traffic to the VPN endpoint), and receives 5000 bytes (500 + // packets) from it. Including overhead that is 6600/5500 bytes. + // VPN sent 8250 bytes (750 packets), and received 8800 (800 packets) over WiFi. + // Of 8250 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes + // attributed to UID_BLUE, and 6750 bytes attributed to UID_VPN. + // Of 8800 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes + // attributed to UID_BLUE, and 5800 bytes attributed to UID_VPN. + final NetworkStats tunStats = + parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_own_traffic); + + assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L); + assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 5800L, 500L, 6750L, 600L); + } + + @Test + public void testVpnWithOneUnderlyingIface_withCompression() throws Exception { + // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE). + final UnderlyingNetworkInfo[] underlyingNetworkInfos = + new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. + // 3000 bytes (300 packets) were sent/received by UID_BLUE over VPN. + // VPN sent/received 1000 bytes (100 packets) over WiFi. + // Of 1000 bytes over WiFi, expect 250 bytes attributed UID_RED and 750 bytes to UID_BLUE, + // with nothing attributed to UID_VPN for both rx/tx traffic. + final NetworkStats tunStats = + parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_compression); + + assertValues(tunStats, TEST_IFACE, UID_RED, 250L, 25L, 250L, 25L); + assertValues(tunStats, TEST_IFACE, UID_BLUE, 750L, 75L, 750L, 75L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L); + } + + @Test + public void testVpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is duplicating traffic across both WiFi and Cell. + final UnderlyingNetworkInfo[] underlyingNetworkInfos = + new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent/received by UID_RED and UID_BLUE over VPN. + // VPN sent/received 4400 bytes (400 packets) over both WiFi and Cell (8800 bytes in total). + // Of 8800 bytes over WiFi/Cell, expect: + // - 500 bytes rx/tx each over WiFi/Cell attributed to both UID_RED and UID_BLUE. + // - 1200 bytes rx/tx each over WiFi/Cell for VPN_UID. + final NetworkStats tunStats = + parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_duplication); + + assertValues(tunStats, TEST_IFACE, UID_RED, 500L, 50L, 500L, 50L); + assertValues(tunStats, TEST_IFACE, UID_BLUE, 500L, 50L, 500L, 50L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 1200L, 100L, 1200L, 100L); + assertValues(tunStats, TEST_IFACE2, UID_RED, 500L, 50L, 500L, 50L); + assertValues(tunStats, TEST_IFACE2, UID_BLUE, 500L, 50L, 500L, 50L); + assertValues(tunStats, TEST_IFACE2, UID_VPN, 1200L, 100L, 1200L, 100L); + } + + @Test + public void testConcurrentVpns() throws Exception { + // Assume two VPNs are connected on two different network interfaces. VPN1 is using + // TEST_IFACE and VPN2 is using TEST_IFACE2. + final UnderlyingNetworkInfo[] underlyingNetworkInfos = new UnderlyingNetworkInfo[] { + createVpnInfo(TUN_IFACE, new String[] {TEST_IFACE}), + createVpnInfo(TUN_IFACE2, new String[] {TEST_IFACE2})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED + // over VPN1. + // 700 bytes (70 packets) were sent, and 3000 bytes (300 packets) were received by UID_RED + // over VPN2. + // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE + // over VPN1. + // 250 bytes (25 packets) were sent, and 500 bytes (50 packets) were received by UID_BLUE + // over VPN2. + // VPN1 sent 1650 bytes (150 packets), and received 3300 (300 packets) over TEST_IFACE. + // Of 1650 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes + // attributed to UID_BLUE, and 150 bytes attributed to UID_VPN. + // Of 3300 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes + // attributed to UID_BLUE, and 300 bytes attributed to UID_VPN. + // VPN2 sent 1045 bytes (95 packets), and received 3850 (350 packets) over TEST_IFACE2. + // Of 1045 bytes sent over Cell, expect 700 bytes attributed to UID_RED, 250 bytes + // attributed to UID_BLUE, and 95 bytes attributed to UID_VPN. + // Of 3850 bytes received over Cell, expect 3000 bytes attributed to UID_RED, 500 bytes + // attributed to UID_BLUE, and 350 bytes attributed to UID_VPN. + final NetworkStats tunStats = + parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_two_vpn); + + assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L); + assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L); + assertValues(tunStats, TEST_IFACE2, UID_RED, 3000L, 300L, 700L, 70L); + assertValues(tunStats, TEST_IFACE2, UID_BLUE, 500L, 50L, 250L, 25L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 150L, 0L); + assertValues(tunStats, TEST_IFACE2, UID_VPN, 350L, 0L, 95L, 0L); + } + + @Test + public void testVpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell. + final UnderlyingNetworkInfo[] underlyingNetworkInfos = + new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent, and 500 bytes (50 packets) received by UID_RED over + // VPN. + // VPN sent 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell. + // And, it received 330 bytes (30 packets) over WiFi and 220 bytes (20 packets) over Cell. + // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for sent (tx) + // traffic. For received (rx) traffic, expect 300 bytes over WiFi and 200 bytes over Cell. + // + // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for tx traffic. + // And, 30 bytes over WiFi and 20 bytes over Cell for rx traffic. + final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_split); + + assertValues(tunStats, TEST_IFACE, UID_RED, 300L, 30L, 600L, 60L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 30L, 0L, 60L, 0L); + assertValues(tunStats, TEST_IFACE2, UID_RED, 200L, 20L, 400L, 40L); + assertValues(tunStats, TEST_IFACE2, UID_VPN, 20L, 0L, 40L, 0L); + } + + @Test + public void testVpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception { + // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and + // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set. + // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell. + final UnderlyingNetworkInfo[] underlyingNetworkInfos = + new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface: + // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. + // VPN sent/received 600 bytes (60 packets) over WiFi and 200 bytes (20 packets) over Cell. + // For UID_RED, expect 600 bytes attributed over WiFi and 200 bytes over Cell for both + // rx/tx. + // UID_VPN gets nothing attributed to it (avoiding negative stats). + final NetworkStats tunStats = + parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_split_compression); + + assertValues(tunStats, TEST_IFACE, UID_RED, 600L, 60L, 600L, 60L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L); + assertValues(tunStats, TEST_IFACE2, UID_RED, 200L, 20L, 200L, 20L); + assertValues(tunStats, TEST_IFACE2, UID_VPN, 0L, 0L, 0L, 0L); + } + + @Test + public void testVpnWithIncorrectUnderlyingIface() throws Exception { + // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2), + // but has declared only WiFi (TEST_IFACE) in its underlying network set. + final UnderlyingNetworkInfo[] underlyingNetworkInfos = + new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})}; + mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos); + + // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption + // overhead per packet): + // 1000 bytes (100 packets) were sent/received by UID_RED over VPN. + // VPN sent/received 1100 bytes (100 packets) over Cell. + // Of 1100 bytes over Cell, expect all of it attributed to UID_VPN for both rx/tx traffic. + final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_incorrect_iface); + + assertValues(tunStats, TEST_IFACE, UID_RED, 0L, 0L, 0L, 0L); + assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L); + assertValues(tunStats, TEST_IFACE2, UID_RED, 0L, 0L, 0L, 0L); + assertValues(tunStats, TEST_IFACE2, UID_VPN, 1100L, 100L, 1100L, 100L); + } + + @Test + public void testKernelTags() throws Exception { + assertEquals(0, kernelToTag("0x0000000000000000")); + assertEquals(0x32, kernelToTag("0x0000003200000000")); + assertEquals(2147483647, kernelToTag("0x7fffffff00000000")); + assertEquals(0, kernelToTag("0x0000000000000000")); + assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000")); + + assertEquals(0, kernelToTag("0x0")); + assertEquals(0, kernelToTag("0xf00d")); + assertEquals(1, kernelToTag("0x100000000")); + assertEquals(14438007, kernelToTag("0xdc4e7700000000")); + assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000")); + } + + @Test + public void testNetworkStatsWithSet() throws Exception { + final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical); + assertEquals(70, stats.size()); + assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L, + 676L); + assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L); + } + + @Test + public void testNetworkStatsSingle() throws Exception { + stageFile(R.raw.xt_qtaguid_iface_typical, file("net/xt_qtaguid/iface_stat_all")); + + final NetworkStats stats = mFactory.readNetworkStatsSummaryDev(); + assertEquals(6, stats.size()); + assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L); + assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L); + assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L); + } + + @Test + public void testNetworkStatsXt() throws Exception { + stageFile(R.raw.xt_qtaguid_iface_fmt_typical, file("net/xt_qtaguid/iface_stat_fmt")); + + final NetworkStats stats = mFactory.readNetworkStatsSummaryXt(); + assertEquals(3, stats.size()); + assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L); + assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L, + 2468L); + assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L); + } + + @Test + public void testDoubleClatAccountingSimple() throws Exception { + mFactory.noteStackedIface("v4-wlan0", "wlan0"); + + // xt_qtaguid_with_clat_simple is a synthetic file that simulates + // - 213 received 464xlat packets of size 200 bytes + // - 41 sent 464xlat packets of size 100 bytes + // - no other traffic on base interface for root uid. + NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_simple); + assertEquals(3, stats.size()); + + assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 46860L, 4920L); + assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L); + } + + @Test + public void testDoubleClatAccounting() throws Exception { + mFactory.noteStackedIface("v4-wlan0", "wlan0"); + + NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat); + assertEquals(42, stats.size()); + + assertStatsEntry(stats, "v4-wlan0", 0, SET_DEFAULT, 0x0, 356L, 276L); + assertStatsEntry(stats, "v4-wlan0", 1000, SET_DEFAULT, 0x0, 30812L, 2310L); + assertStatsEntry(stats, "v4-wlan0", 10102, SET_DEFAULT, 0x0, 10022L, 3330L); + assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 9532772L, 254112L); + assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L); + assertStatsEntry(stats, "wlan0", 1000, SET_DEFAULT, 0x0, 6126L, 2013L); + assertStatsEntry(stats, "wlan0", 10013, SET_DEFAULT, 0x0, 0L, 144L); + assertStatsEntry(stats, "wlan0", 10018, SET_DEFAULT, 0x0, 5980263L, 167667L); + assertStatsEntry(stats, "wlan0", 10060, SET_DEFAULT, 0x0, 134356L, 8705L); + assertStatsEntry(stats, "wlan0", 10079, SET_DEFAULT, 0x0, 10926L, 1507L); + assertStatsEntry(stats, "wlan0", 10102, SET_DEFAULT, 0x0, 25038L, 8245L); + assertStatsEntry(stats, "wlan0", 10103, SET_DEFAULT, 0x0, 0L, 192L); + assertStatsEntry(stats, "dummy0", 0, SET_DEFAULT, 0x0, 0L, 168L); + assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L); + + assertNoStatsEntry(stats, "wlan0", 1029, SET_DEFAULT, 0x0); + } + + @Test + public void testDoubleClatAccounting100MBDownload() throws Exception { + // Downloading 100mb from an ipv4 only destination in a foreground activity + + long appRxBytesBefore = 328684029L; + long appRxBytesAfter = 439237478L; + assertEquals("App traffic should be ~100MB", 110553449, appRxBytesAfter - appRxBytesBefore); + + long rootRxBytes = 330187296L; + + mFactory.noteStackedIface("v4-wlan0", "wlan0"); + NetworkStats stats; + + // Stats snapshot before the download + stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_before); + assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesBefore, 5199872L); + assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytes, 0L); + + // Stats snapshot after the download + stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_after); + assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L); + assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytes, 0L); + } + + /** + * Copy a {@link Resources#openRawResource(int)} into {@link File} for + * testing purposes. + */ + private void stageFile(int rawId, File file) throws Exception { + new File(file.getParent()).mkdirs(); + InputStream in = null; + OutputStream out = null; + try { + in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId); + out = new FileOutputStream(file); + Streams.copy(in, out); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + } + } + + private void stageLong(long value, File file) throws Exception { + new File(file.getParent()).mkdirs(); + FileWriter out = null; + try { + out = new FileWriter(file); + out.write(Long.toString(value)); + } finally { + IoUtils.closeQuietly(out); + } + } + + private File file(String path) throws Exception { + return new File(mTestProc, path); + } + + private NetworkStats parseDetailedStats(int resourceId) throws Exception { + stageFile(resourceId, file("net/xt_qtaguid/stats")); + return mFactory.readNetworkStatsDetail(); + } + + private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set, + int tag, long rxBytes, long txBytes) { + final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO); + if (i < 0) { + fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)", + iface, uid, set, tag)); + } + final NetworkStats.Entry entry = stats.getValues(i, null); + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + } + + private static void assertNoStatsEntry(NetworkStats stats, String iface, int uid, int set, + int tag) { + final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO); + if (i >= 0) { + fail("unexpected NetworkStats entry at " + i); + } + } + + private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set, + int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) { + assertStatsEntry(stats, iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, + rxBytes, rxPackets, txBytes, txPackets); + } + + private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set, + int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, + long txBytes, long txPackets) { + final int i = stats.findIndex(iface, uid, set, tag, metered, roaming, defaultNetwork); + + if (i < 0) { + fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d, metered:" + + " %d, roaming: %d, defaultNetwork: %d)", + iface, uid, set, tag, metered, roaming, defaultNetwork)); + } + final NetworkStats.Entry entry = stats.getValues(i, null); + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + assertEquals("unexpected txPackets", txPackets, entry.txPackets); + } +} diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java new file mode 100644 index 0000000000..9fa1c50423 --- /dev/null +++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.NetworkIdentity.OEM_NONE; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.net.NetworkTemplate.buildTemplateWifiWildcard; +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; + +import android.app.usage.NetworkStatsManager; +import android.net.DataUsageRequest; +import android.net.NetworkIdentity; +import android.net.NetworkStats; +import android.net.NetworkTemplate; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Messenger; +import android.os.Process; +import android.os.UserHandle; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.net.NetworkStatsServiceTest.LatchedHandler; +import com.android.testutils.HandlerUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +/** + * Tests for {@link NetworkStatsObservers}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsObserversTest { + private static final String TEST_IFACE = "test0"; + private static final String TEST_IFACE2 = "test1"; + private static final long TEST_START = 1194220800000L; + + private static final String IMSI_1 = "310004"; + private static final String IMSI_2 = "310260"; + private static final String TEST_SSID = "AndroidAP"; + + private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard(); + private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1); + private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2); + + private static final int UID_RED = UserHandle.PER_USER_RANGE + 1; + private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2; + private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3; + private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4; + + private static final long WAIT_TIMEOUT_MS = 500; + private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES; + private static final long BASE_BYTES = 7 * MB_IN_BYTES; + private static final int INVALID_TYPE = -1; + + private long mElapsedRealtime; + + private HandlerThread mObserverHandlerThread; + private Handler mObserverNoopHandler; + + private LatchedHandler mHandler; + + private NetworkStatsObservers mStatsObservers; + private Messenger mMessenger; + private ArrayMap mActiveIfaces; + private ArrayMap mActiveUidIfaces; + + @Mock private IBinder mockBinder; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mObserverHandlerThread = new HandlerThread("HandlerThread"); + mObserverHandlerThread.start(); + final Looper observerLooper = mObserverHandlerThread.getLooper(); + mStatsObservers = new NetworkStatsObservers() { + @Override + protected Looper getHandlerLooperLocked() { + return observerLooper; + } + }; + + mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable()); + mMessenger = new Messenger(mHandler); + + mActiveIfaces = new ArrayMap<>(); + mActiveUidIfaces = new ArrayMap<>(); + } + + @Test + public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception { + long thresholdTooLowBytes = 1L; + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateWifi, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + } + + @Test + public void testRegister_highThreshold_accepted() throws Exception { + long highThresholdBytes = 2 * THRESHOLD_BYTES; + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateWifi, request.template)); + assertEquals(highThresholdBytes, request.thresholdInBytes); + } + + @Test + public void testRegister_twoRequests_twoIds() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES); + + DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE); + assertTrue(request1.requestId > 0); + assertTrue(Objects.equals(sTemplateWifi, request1.template)); + assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes); + + DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE); + assertTrue(request2.requestId > request1.requestId); + assertTrue(Objects.equals(sTemplateWifi, request2.template)); + assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes); + } + + @Test + public void testUnregister_unknownRequest_noop() throws Exception { + DataUsageRequest unknownRequest = new DataUsageRequest( + 123456 /* id */, sTemplateWifi, THRESHOLD_BYTES); + + mStatsObservers.unregister(unknownRequest, UID_RED); + } + + @Test + public void testUnregister_knownRequest_releasesCaller() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt()); + + mStatsObservers.unregister(request, Process.SYSTEM_UID); + waitForObserverToIdle(); + + Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt()); + } + + @Test + public void testUnregister_knownRequest_invalidUid_doesNotUnregister() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + UID_RED, NetworkStatsAccess.Level.DEVICE); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt()); + + mStatsObservers.unregister(request, UID_BLUE); + waitForObserverToIdle(); + + Mockito.verifyZeroInteractions(mockBinder); + } + + private NetworkIdentitySet makeTestIdentSet() { + NetworkIdentitySet identSet = new NetworkIdentitySet(); + identSet.add(new NetworkIdentity( + TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, + IMSI_1, null /* networkId */, false /* roaming */, true /* metered */, + true /* defaultNetwork */, OEM_NONE)); + return identSet; + } + + @Test + public void testUpdateStats_initialSample_doesNotNotify() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + + NetworkIdentitySet identSet = makeTestIdentSet(); + mActiveIfaces.put(TEST_IFACE, identSet); + + // Baseline + NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */) + .insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); + NetworkStats uidSnapshot = null; + + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + waitForObserverToIdle(); + } + + @Test + public void testUpdateStats_belowThreshold_doesNotNotify() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + + NetworkIdentitySet identSet = makeTestIdentSet(); + mActiveIfaces.put(TEST_IFACE, identSet); + + // Baseline + NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */) + .insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); + NetworkStats uidSnapshot = null; + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + + // Delta + xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */) + .insertEntry(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + waitForObserverToIdle(); + } + + + @Test + public void testUpdateStats_deviceAccess_notifies() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + + NetworkIdentitySet identSet = makeTestIdentSet(); + mActiveIfaces.put(TEST_IFACE, identSet); + + // Baseline + NetworkStats xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */) + .insertEntry(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L); + NetworkStats uidSnapshot = null; + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + + // Delta + xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */) + .insertEntry(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L, + BASE_BYTES + THRESHOLD_BYTES, 22L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + waitForObserverToIdle(); + assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType); + } + + @Test + public void testUpdateStats_defaultAccess_notifiesSameUid() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + UID_RED, NetworkStatsAccess.Level.DEFAULT); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + + NetworkIdentitySet identSet = makeTestIdentSet(); + mActiveUidIfaces.put(TEST_IFACE, identSet); + + // Baseline + NetworkStats xtSnapshot = null; + NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + + // Delta + uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, + BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + waitForObserverToIdle(); + assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType); + } + + @Test + public void testUpdateStats_defaultAccess_usageOtherUid_doesNotNotify() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + UID_BLUE, NetworkStatsAccess.Level.DEFAULT); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + + NetworkIdentitySet identSet = makeTestIdentSet(); + mActiveUidIfaces.put(TEST_IFACE, identSet); + + // Baseline + NetworkStats xtSnapshot = null; + NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + + // Delta + uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, + BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + waitForObserverToIdle(); + } + + @Test + public void testUpdateStats_userAccess_usageSameUser_notifies() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + UID_BLUE, NetworkStatsAccess.Level.USER); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + + NetworkIdentitySet identSet = makeTestIdentSet(); + mActiveUidIfaces.put(TEST_IFACE, identSet); + + // Baseline + NetworkStats xtSnapshot = null; + NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + + // Delta + uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L, + BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + waitForObserverToIdle(); + assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType); + } + + @Test + public void testUpdateStats_userAccess_usageAnotherUser_doesNotNotify() throws Exception { + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES); + + DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder, + UID_RED, NetworkStatsAccess.Level.USER); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateImsi1, request.template)); + assertEquals(THRESHOLD_BYTES, request.thresholdInBytes); + + NetworkIdentitySet identSet = makeTestIdentSet(); + mActiveUidIfaces.put(TEST_IFACE, identSet); + + // Baseline + NetworkStats xtSnapshot = null; + NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) + .insertEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + + // Delta + uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) + .insertEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, + BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); + mStatsObservers.updateStats( + xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); + waitForObserverToIdle(); + } + + private void waitForObserverToIdle() { + HandlerUtils.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS); + HandlerUtils.waitForIdle(mHandler, WAIT_TIMEOUT_MS); + } +} diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java new file mode 100644 index 0000000000..fd374bc9e6 --- /dev/null +++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java @@ -0,0 +1,1767 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.content.Intent.ACTION_UID_REMOVED; +import static android.content.Intent.EXTRA_UID; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.NetworkIdentity.OEM_PAID; +import static android.net.NetworkIdentity.OEM_PRIVATE; +import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.INTERFACES_ALL; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkStats.ROAMING_ALL; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.ROAMING_YES; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.SET_FOREGROUND; +import static android.net.NetworkStats.STATS_PER_UID; +import static android.net.NetworkStats.TAG_ALL; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStatsHistory.FIELD_ALL; +import static android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD; +import static android.net.NetworkTemplate.NETWORK_TYPE_ALL; +import static android.net.NetworkTemplate.OEM_MANAGED_NO; +import static android.net.NetworkTemplate.OEM_MANAGED_YES; +import static android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT; +import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.net.NetworkTemplate.buildTemplateMobileWithRatType; +import static android.net.NetworkTemplate.buildTemplateWifi; +import static android.net.NetworkTemplate.buildTemplateWifiWildcard; +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.net.TrafficStats.UID_REMOVED; +import static android.net.TrafficStats.UID_TETHERING; +import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static android.text.format.DateUtils.HOUR_IN_MILLIS; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import static android.text.format.DateUtils.WEEK_IN_MILLIS; + +import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.app.AlarmManager; +import android.app.usage.NetworkStatsManager; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.net.DataUsageRequest; +import android.net.INetworkManagementEventObserver; +import android.net.INetworkStatsSession; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkStateSnapshot; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TelephonyNetworkSpecifier; +import android.net.UnderlyingNetworkInfo; +import android.net.netstats.provider.INetworkStatsProviderCallback; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.PowerManager; +import android.os.SimpleClock; +import android.provider.Settings; +import android.telephony.TelephonyManager; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.server.net.NetworkStatsService.NetworkStatsSettings; +import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config; +import com.android.testutils.HandlerUtils; +import com.android.testutils.TestableNetworkStatsProviderBinder; + +import libcore.io.IoUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.time.Clock; +import java.time.ZoneOffset; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Tests for {@link NetworkStatsService}. + * + * TODO: This test used to be really brittle because it used Easymock - it uses Mockito now, but + * still uses the Easymock structure, which could be simplified. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetworkStatsServiceTest extends NetworkStatsBaseTest { + private static final String TAG = "NetworkStatsServiceTest"; + + private static final long TEST_START = 1194220800000L; + + private static final String IMSI_1 = "310004"; + private static final String IMSI_2 = "310260"; + private static final String TEST_SSID = "AndroidAP"; + + private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID); + private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1); + private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2); + + private static final Network WIFI_NETWORK = new Network(100); + private static final Network MOBILE_NETWORK = new Network(101); + private static final Network VPN_NETWORK = new Network(102); + + private static final Network[] NETWORKS_WIFI = new Network[]{ WIFI_NETWORK }; + private static final Network[] NETWORKS_MOBILE = new Network[]{ MOBILE_NETWORK }; + + private static final long WAIT_TIMEOUT = 2 * 1000; // 2 secs + private static final int INVALID_TYPE = -1; + + private long mElapsedRealtime; + + private File mStatsDir; + private MockContext mServiceContext; + private @Mock TelephonyManager mTelephonyManager; + private @Mock INetworkManagementService mNetManager; + private @Mock NetworkStatsFactory mStatsFactory; + private @Mock NetworkStatsSettings mSettings; + private @Mock IBinder mBinder; + private @Mock AlarmManager mAlarmManager; + @Mock + private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor; + private HandlerThread mHandlerThread; + + private NetworkStatsService mService; + private INetworkStatsSession mSession; + private INetworkManagementEventObserver mNetworkObserver; + private ContentObserver mContentObserver; + private Handler mHandler; + + private class MockContext extends BroadcastInterceptingContext { + private final Context mBaseContext; + + MockContext(Context base) { + super(base); + mBaseContext = base; + } + + @Override + public Object getSystemService(String name) { + if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; + return mBaseContext.getSystemService(name); + } + } + + private final Clock mClock = new SimpleClock(ZoneOffset.UTC) { + @Override + public long millis() { + return currentTimeMillis(); + } + }; + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + final Context context = InstrumentationRegistry.getContext(); + mServiceContext = new MockContext(context); + mStatsDir = context.getFilesDir(); + if (mStatsDir.exists()) { + IoUtils.deleteContents(mStatsDir); + } + + PowerManager powerManager = (PowerManager) mServiceContext.getSystemService( + Context.POWER_SERVICE); + PowerManager.WakeLock wakeLock = + powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + + mHandlerThread = new HandlerThread("HandlerThread"); + final NetworkStatsService.Dependencies deps = makeDependencies(); + mService = new NetworkStatsService(mServiceContext, mNetManager, mAlarmManager, wakeLock, + mClock, mSettings, mStatsFactory, new NetworkStatsObservers(), mStatsDir, + getBaseDir(mStatsDir), deps); + + mElapsedRealtime = 0L; + + expectDefaultSettings(); + expectNetworkStatsUidDetail(buildEmptyStats()); + expectSystemReady(); + mService.systemReady(); + // Verify that system ready fetches realtime stats + verify(mStatsFactory).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL); + // Wait for posting onChange() event to handler thread and verify that when system ready, + // start monitoring data usage per RAT type because the settings value is mock as false + // by default in expectSettings(). + waitForIdle(); + verify(mNetworkStatsSubscriptionsMonitor).start(); + reset(mNetworkStatsSubscriptionsMonitor); + + doReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS).when(mTelephonyManager) + .checkCarrierPrivilegesForPackageAnyPhone(anyString()); + + mSession = mService.openSession(); + assertNotNull("openSession() failed", mSession); + + // catch INetworkManagementEventObserver during systemReady() + ArgumentCaptor networkObserver = + ArgumentCaptor.forClass(INetworkManagementEventObserver.class); + verify(mNetManager).registerObserver(networkObserver.capture()); + mNetworkObserver = networkObserver.getValue(); + } + + @NonNull + private NetworkStatsService.Dependencies makeDependencies() { + return new NetworkStatsService.Dependencies() { + @Override + public HandlerThread makeHandlerThread() { + return mHandlerThread; + } + + @Override + public NetworkStatsSubscriptionsMonitor makeSubscriptionsMonitor( + @NonNull Context context, @NonNull Looper looper, @NonNull Executor executor, + @NonNull NetworkStatsService service) { + + return mNetworkStatsSubscriptionsMonitor; + } + + @Override + public ContentObserver makeContentObserver(Handler handler, + NetworkStatsSettings settings, NetworkStatsSubscriptionsMonitor monitor) { + mHandler = handler; + return mContentObserver = super.makeContentObserver(handler, settings, monitor); + } + + }; + } + + @After + public void tearDown() throws Exception { + IoUtils.deleteContents(mStatsDir); + + mServiceContext = null; + mStatsDir = null; + + mNetManager = null; + mSettings = null; + + mSession.close(); + mService = null; + + mHandlerThread.quitSafely(); + } + + @Test + public void testNetworkStatsWifi() throws Exception { + // pretend that wifi network comes online; service should ask about full + // network state, and poll any existing interfaces before updating. + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // verify service has empty history for wifi + assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); + + // modify some number on wifi, and trigger poll event + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 1024L, 1L, 2048L, 2L)); + expectNetworkStatsUidDetail(buildEmptyStats()); + forcePollAndWaitForIdle(); + + // verify service recorded history + assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0); + + + // and bump forward again, with counters going higher. this is + // important, since polling should correctly subtract last snapshot. + incrementCurrentTime(DAY_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 4096L, 4L, 8192L, 8L)); + expectNetworkStatsUidDetail(buildEmptyStats()); + forcePollAndWaitForIdle(); + + // verify service recorded history + assertNetworkTotal(sTemplateWifi, 4096L, 4L, 8192L, 8L, 0); + + } + + @Test + public void testStatsRebootPersist() throws Exception { + assertStatsFilesExist(false); + + // pretend that wifi network comes online; service should ask about full + // network state, and poll any existing interfaces before updating. + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // verify service has empty history for wifi + assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); + + + // modify some number on wifi, and trigger poll event + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L)); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L)); + mService.setUidForeground(UID_RED, false); + mService.incrementOperationCount(UID_RED, 0xFAAD, 4); + mService.setUidForeground(UID_RED, true); + mService.incrementOperationCount(UID_RED, 0xFAAD, 6); + + forcePollAndWaitForIdle(); + + // verify service recorded history + assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0); + assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10); + assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 512L, 4L, 256L, 2L, 4); + assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 512L, 4L, 256L, 2L, 6); + assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0); + + + // graceful shutdown system, which should trigger persist of stats, and + // clear any values in memory. + expectDefaultSettings(); + mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN)); + assertStatsFilesExist(true); + + // boot through serviceReady() again + expectDefaultSettings(); + expectNetworkStatsUidDetail(buildEmptyStats()); + expectSystemReady(); + + mService.systemReady(); + + // after systemReady(), we should have historical stats loaded again + assertNetworkTotal(sTemplateWifi, 1024L, 8L, 2048L, 16L, 0); + assertUidTotal(sTemplateWifi, UID_RED, 1024L, 8L, 512L, 4L, 10); + assertUidTotal(sTemplateWifi, UID_RED, SET_DEFAULT, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 512L, 4L, 256L, 2L, 4); + assertUidTotal(sTemplateWifi, UID_RED, SET_FOREGROUND, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 512L, 4L, 256L, 2L, 6); + assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 1L, 128L, 1L, 0); + + } + + // TODO: simulate reboot to test bucket resize + @Test + @Ignore + public void testStatsBucketResize() throws Exception { + NetworkStatsHistory history = null; + + assertStatsFilesExist(false); + + // pretend that wifi network comes online; service should ask about full + // network state, and poll any existing interfaces before updating. + expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // modify some number on wifi, and trigger poll event + incrementCurrentTime(2 * HOUR_IN_MILLIS); + expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 512L, 4L, 512L, 4L)); + expectNetworkStatsUidDetail(buildEmptyStats()); + forcePollAndWaitForIdle(); + + // verify service recorded history + history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL); + assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0); + assertEquals(HOUR_IN_MILLIS, history.getBucketDuration()); + assertEquals(2, history.size()); + + + // now change bucket duration setting and trigger another poll with + // exact same values, which should resize existing buckets. + expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS); + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + forcePollAndWaitForIdle(); + + // verify identical stats, but spread across 4 buckets now + history = mSession.getHistoryForNetwork(sTemplateWifi, FIELD_ALL); + assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0); + assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration()); + assertEquals(4, history.size()); + + } + + @Test + public void testUidStatsAcrossNetworks() throws Exception { + // pretend first mobile network comes online + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildMobile3gState(IMSI_1)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // create some traffic on first network + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 2048L, 16L, 512L, 4L)); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); + mService.incrementOperationCount(UID_RED, 0xF00D, 10); + + forcePollAndWaitForIdle(); + + // verify service recorded history + assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0); + assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); + assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10); + assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0); + + + // now switch networks; this also tests that we're okay with interfaces + // disappearing, to verify we don't count backwards. + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + states = new NetworkStateSnapshot[] {buildMobile3gState(IMSI_2)}; + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 2048L, 16L, 512L, 4L)); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); + + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + forcePollAndWaitForIdle(); + + + // create traffic on second network + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 2176L, 17L, 1536L, 12L)); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L)); + mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10); + + forcePollAndWaitForIdle(); + + // verify original history still intact + assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0); + assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 512L, 4L, 10); + assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 4L, 0L, 0L, 0); + + // and verify new history also recorded under different template, which + // verifies that we didn't cross the streams. + assertNetworkTotal(sTemplateImsi2, 128L, 1L, 1024L, 8L, 0); + assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); + assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1L, 1024L, 8L, 10); + + } + + @Test + public void testUidRemovedIsMoved() throws Exception { + // pretend that network comes online + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // create some traffic + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 4128L, 258L, 544L, 34L)); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, + 4096L, 258L, 512L, 32L, 0L) + .insertEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); + mService.incrementOperationCount(UID_RED, 0xFAAD, 10); + + forcePollAndWaitForIdle(); + + // verify service recorded history + assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0); + assertUidTotal(sTemplateWifi, UID_RED, 16L, 1L, 16L, 1L, 10); + assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0); + assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0); + + + // now pretend two UIDs are uninstalled, which should migrate stats to + // special "removed" bucket. + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 4128L, 258L, 544L, 34L)); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, + 4096L, 258L, 512L, 32L, 0L) + .insertEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); + final Intent intent = new Intent(ACTION_UID_REMOVED); + intent.putExtra(EXTRA_UID, UID_BLUE); + mServiceContext.sendBroadcast(intent); + intent.putExtra(EXTRA_UID, UID_RED); + mServiceContext.sendBroadcast(intent); + + // existing uid and total should remain unchanged; but removed UID + // should be gone completely. + assertNetworkTotal(sTemplateWifi, 4128L, 258L, 544L, 34L, 0); + assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L, 0L, 0L, 0); + assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L, 0L, 0L, 0); + assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0); + assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 259L, 528L, 33L, 10); + + } + + @Test + public void testMobileStatsByRatType() throws Exception { + final NetworkTemplate template3g = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS); + final NetworkTemplate template4g = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_LTE); + final NetworkTemplate template5g = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_NR); + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; + + // 3G network comes online. + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 12L, 18L, 14L, 1L, 0L))); + forcePollAndWaitForIdle(); + + // Verify 3g templates gets stats. + assertUidTotal(sTemplateImsi1, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(template4g, UID_RED, 0L, 0L, 0L, 0L, 0); + assertUidTotal(template5g, UID_RED, 0L, 0L, 0L, 0L, 0); + + // 4G network comes online. + incrementCurrentTime(MINUTE_IN_MILLIS); + setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_LTE); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + // Append more traffic on existing 3g stats entry. + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 16L, 22L, 17L, 2L, 0L)) + // Add entry that is new on 4g. + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, + 33L, 27L, 8L, 10L, 1L))); + forcePollAndWaitForIdle(); + + // Verify ALL_MOBILE template gets all. 3g template counters do not increase. + assertUidTotal(sTemplateImsi1, UID_RED, 49L, 49L, 25L, 12L, 1); + assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0); + // Verify 4g template counts appended stats on existing entry and newly created entry. + assertUidTotal(template4g, UID_RED, 4L + 33L, 4L + 27L, 3L + 8L, 1L + 10L, 1); + // Verify 5g template doesn't get anything since no traffic is generated on 5g. + assertUidTotal(template5g, UID_RED, 0L, 0L, 0L, 0L, 0); + + // 5g network comes online. + incrementCurrentTime(MINUTE_IN_MILLIS); + setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_NR); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + // Existing stats remains. + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 16L, 22L, 17L, 2L, 0L)) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, + 33L, 27L, 8L, 10L, 1L)) + // Add some traffic on 5g. + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 5L, 13L, 31L, 9L, 2L))); + forcePollAndWaitForIdle(); + + // Verify ALL_MOBILE template gets all. + assertUidTotal(sTemplateImsi1, UID_RED, 54L, 62L, 56L, 21L, 3); + // 3g/4g template counters do not increase. + assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(template4g, UID_RED, 4L + 33L, 4L + 27L, 3L + 8L, 1L + 10L, 1); + // Verify 5g template gets the 5g count. + assertUidTotal(template5g, UID_RED, 5L, 13L, 31L, 9L, 2); + } + + @Test + public void testMobileStatsOemManaged() throws Exception { + final NetworkTemplate templateOemPaid = new NetworkTemplate(MATCH_MOBILE_WILDCARD, + /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null, + METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_PAID, + SUBSCRIBER_ID_MATCH_RULE_EXACT); + + final NetworkTemplate templateOemPrivate = new NetworkTemplate(MATCH_MOBILE_WILDCARD, + /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null, + METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_PRIVATE, + SUBSCRIBER_ID_MATCH_RULE_EXACT); + + final NetworkTemplate templateOemAll = new NetworkTemplate(MATCH_MOBILE_WILDCARD, + /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null, + METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, + OEM_PAID | OEM_PRIVATE, SUBSCRIBER_ID_MATCH_RULE_EXACT); + + final NetworkTemplate templateOemYes = new NetworkTemplate(MATCH_MOBILE_WILDCARD, + /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null, + METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_YES, + SUBSCRIBER_ID_MATCH_RULE_EXACT); + + final NetworkTemplate templateOemNone = new NetworkTemplate(MATCH_MOBILE_WILDCARD, + /*subscriberId=*/null, /*matchSubscriberIds=*/null, /*networkId=*/null, + METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_NO, + SUBSCRIBER_ID_MATCH_RULE_EXACT); + + // OEM_PAID network comes online. + NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{ + buildOemManagedMobileState(IMSI_1, false, + new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PAID})}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 36L, 41L, 24L, 96L, 0L))); + forcePollAndWaitForIdle(); + + // OEM_PRIVATE network comes online. + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, + new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE})}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 49L, 71L, 72L, 48L, 0L))); + forcePollAndWaitForIdle(); + + // OEM_PAID + OEM_PRIVATE network comes online. + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, + new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, + NetworkCapabilities.NET_CAPABILITY_OEM_PAID})}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 57L, 86L, 83L, 93L, 0L))); + forcePollAndWaitForIdle(); + + // OEM_NONE network comes online. + states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{})}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 29L, 73L, 34L, 31L, 0L))); + forcePollAndWaitForIdle(); + + // Verify OEM_PAID template gets only relevant stats. + assertUidTotal(templateOemPaid, UID_RED, 36L, 41L, 24L, 96L, 0); + + // Verify OEM_PRIVATE template gets only relevant stats. + assertUidTotal(templateOemPrivate, UID_RED, 49L, 71L, 72L, 48L, 0); + + // Verify OEM_PAID + OEM_PRIVATE template gets only relevant stats. + assertUidTotal(templateOemAll, UID_RED, 57L, 86L, 83L, 93L, 0); + + // Verify OEM_NONE sees only non-OEM managed stats. + assertUidTotal(templateOemNone, UID_RED, 29L, 73L, 34L, 31L, 0); + + // Verify OEM_MANAGED_YES sees all OEM managed stats. + assertUidTotal(templateOemYes, UID_RED, + 36L + 49L + 57L, + 41L + 71L + 86L, + 24L + 72L + 83L, + 96L + 48L + 93L, 0); + + // Verify ALL_MOBILE template gets both OEM managed and non-OEM managed stats. + assertUidTotal(sTemplateImsi1, UID_RED, + 36L + 49L + 57L + 29L, + 41L + 71L + 86L + 73L, + 24L + 72L + 83L + 34L, + 96L + 48L + 93L + 31L, 0); + } + + // TODO: support per IMSI state + private void setMobileRatTypeAndWaitForIdle(int ratType) { + when(mNetworkStatsSubscriptionsMonitor.getRatTypeForSubscriberId(anyString())) + .thenReturn(ratType); + mService.handleOnCollapsedRatTypeChanged(); + HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT); + } + + @Test + public void testSummaryForAllUid() throws Exception { + // pretend that network comes online + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // create some traffic for two apps + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L)); + mService.incrementOperationCount(UID_RED, 0xF00D, 1); + + forcePollAndWaitForIdle(); + + // verify service recorded history + assertUidTotal(sTemplateWifi, UID_RED, 50L, 5L, 50L, 5L, 1); + assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 8L, 512L, 4L, 0); + + + // now create more traffic in next hour, but only for one app + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, + 2048L, 16L, 1024L, 8L, 0L)); + forcePollAndWaitForIdle(); + + // first verify entire history present + NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(3, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 50L, 5L, 50L, 5L, 1); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 10L, 1L, 10L, 1L, 1); + assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 2048L, 16L, 1024L, 8L, 0); + + // now verify that recent history only contains one uid + final long currentTime = currentTimeMillis(); + stats = mSession.getSummaryForAllUid( + sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true); + assertEquals(1, stats.size()); + assertValues(stats, IFACE_ALL, UID_BLUE, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1024L, 8L, 512L, 4L, 0); + } + + @Test + public void testDetailedUidStats() throws Exception { + // pretend that network comes online + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + NetworkStats.Entry entry1 = new NetworkStats.Entry( + TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L); + NetworkStats.Entry entry2 = new NetworkStats.Entry( + TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 50L, 5L, 50L, 5L, 0L); + NetworkStats.Entry entry3 = new NetworkStats.Entry( + TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xBEEF, 1024L, 8L, 512L, 4L, 0L); + + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) + .insertEntry(entry1) + .insertEntry(entry2) + .insertEntry(entry3)); + mService.incrementOperationCount(UID_RED, 0xF00D, 1); + + NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL); + + assertEquals(3, stats.size()); + entry1.operations = 1; + assertEquals(entry1, stats.getValues(0, null)); + entry2.operations = 1; + assertEquals(entry2, stats.getValues(1, null)); + assertEquals(entry3, stats.getValues(2, null)); + } + + @Test + public void testDetailedUidStats_Filtered() throws Exception { + // pretend that network comes online + expectDefaultSettings(); + + final String stackedIface = "stacked-test0"; + final LinkProperties stackedProp = new LinkProperties(); + stackedProp.setInterfaceName(stackedIface); + final NetworkStateSnapshot wifiState = buildWifiState(); + wifiState.getLinkProperties().addStackedLink(stackedProp); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {wifiState}; + + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + NetworkStats.Entry uidStats = new NetworkStats.Entry( + TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L); + // Stacked on matching interface + NetworkStats.Entry tetheredStats1 = new NetworkStats.Entry( + stackedIface, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L); + // Different interface + NetworkStats.Entry tetheredStats2 = new NetworkStats.Entry( + "otherif", UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L); + + final String[] ifaceFilter = new String[] { TEST_IFACE }; + final String[] augmentedIfaceFilter = new String[] { stackedIface, TEST_IFACE }; + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + when(mStatsFactory.augmentWithStackedInterfaces(eq(ifaceFilter))) + .thenReturn(augmentedIfaceFilter); + when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL))) + .thenReturn(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(uidStats)); + when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)) + .thenReturn(new NetworkStats(getElapsedRealtime(), 2) + .insertEntry(tetheredStats1) + .insertEntry(tetheredStats2)); + + NetworkStats stats = mService.getDetailedUidStats(ifaceFilter); + + // mStatsFactory#readNetworkStatsDetail() has the following invocations: + // 1) NetworkStatsService#systemReady from #setUp. + // 2) mService#forceUpdateIfaces in the test above. + // + // Additionally, we should have one call from the above call to mService#getDetailedUidStats + // with the augmented ifaceFilter. + verify(mStatsFactory, times(2)).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL); + verify(mStatsFactory, times(1)).readNetworkStatsDetail( + eq(UID_ALL), + eq(augmentedIfaceFilter), + eq(TAG_ALL)); + assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE)); + assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface)); + assertEquals(2, stats.size()); + assertEquals(uidStats, stats.getValues(0, null)); + assertEquals(tetheredStats1, stats.getValues(1, null)); + } + + @Test + public void testForegroundBackground() throws Exception { + // pretend that network comes online + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // create some initial traffic + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)); + mService.incrementOperationCount(UID_RED, 0xF00D, 1); + + forcePollAndWaitForIdle(); + + // verify service recorded history + assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1); + + + // now switch to foreground + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L)); + mService.setUidForeground(UID_RED, true); + mService.incrementOperationCount(UID_RED, 0xFAAD, 1); + + forcePollAndWaitForIdle(); + + // test that we combined correctly + assertUidTotal(sTemplateWifi, UID_RED, 160L, 4L, 160L, 4L, 2); + + // verify entire history present + final NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(4, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1); + assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 32L, 2L, 32L, 2L, 1); + assertValues(stats, IFACE_ALL, UID_RED, SET_FOREGROUND, 0xFAAD, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1L, 1L, 1L, 1L, 1); + } + + @Test + public void testMetered() throws Exception { + // pretend that network comes online + expectDefaultSettings(); + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[] {buildWifiState(true /* isMetered */, TEST_IFACE)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // create some initial traffic + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + // Note that all traffic from NetworkManagementService is tagged as METERED_NO, ROAMING_NO + // and DEFAULT_NETWORK_YES, because these three properties aren't tracked at that layer. + // We layer them on top by inspecting the iface properties. + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L)); + mService.incrementOperationCount(UID_RED, 0xF00D, 1); + + forcePollAndWaitForIdle(); + + // verify service recorded history + assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1); + // verify entire history present + final NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(2, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1); + } + + @Test + public void testRoaming() throws Exception { + // pretend that network comes online + expectDefaultSettings(); + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[] {buildMobile3gState(IMSI_1, true /* isRoaming */)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + // Note that all traffic from NetworkManagementService is tagged as METERED_NO and + // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it + // on top by inspecting the iface properties. + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L)); + forcePollAndWaitForIdle(); + + // verify service recorded history + assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0); + + // verify entire history present + final NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(2, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_YES, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_YES, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0); + } + + @Test + public void testTethering() throws Exception { + // pretend first mobile network comes online + expectDefaultSettings(); + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // create some tethering traffic + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST-TETHERING-OFFLOAD", provider); + assertNotNull(cb); + final long now = getElapsedRealtime(); + + // Traffic seen by kernel counters (includes software tethering). + final NetworkStats swIfaceStats = new NetworkStats(now, 1) + .insertEntry(TEST_IFACE, 1536L, 12L, 384L, 3L); + // Hardware tethering traffic, not seen by kernel counters. + final NetworkStats tetherHwIfaceStats = new NetworkStats(now, 1) + .insertEntry(new NetworkStats.Entry(TEST_IFACE, UID_ALL, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 512L, 4L, 128L, 1L, 0L)); + final NetworkStats tetherHwUidStats = new NetworkStats(now, 1) + .insertEntry(new NetworkStats.Entry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 512L, 4L, 128L, 1L, 0L)); + cb.notifyStatsUpdated(0 /* unused */, tetherHwIfaceStats, tetherHwUidStats); + + // Fake some traffic done by apps on the device (as opposed to tethering), and record it + // into UID stats (as opposed to iface stats). + final NetworkStats localUidStats = new NetworkStats(now, 1) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L); + // Software per-uid tethering traffic. + final NetworkStats tetherSwUidStats = new NetworkStats(now, 1) + .insertEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1408L, 10L, 256L, 1L, + 0L); + + expectNetworkStatsSummary(swIfaceStats); + expectNetworkStatsUidDetail(localUidStats, tetherSwUidStats); + forcePollAndWaitForIdle(); + + // verify service recorded history + assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0); + assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0); + assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0); + } + + @Test + public void testRegisterUsageCallback() throws Exception { + // pretend that wifi network comes online; service should ask about full + // network state, and poll any existing interfaces before updating. + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // verify service has empty history for wifi + assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); + long thresholdInBytes = 1L; // very small; should be overriden by framework + DataUsageRequest inputRequest = new DataUsageRequest( + DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdInBytes); + + // Create a messenger that waits for callback activity + ConditionVariable cv = new ConditionVariable(false); + LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv); + Messenger messenger = new Messenger(latchedHandler); + + // Force poll + expectDefaultSettings(); + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + // Register and verify request and that binder was called + DataUsageRequest request = + mService.registerUsageCallback(mServiceContext.getOpPackageName(), inputRequest, + messenger, mBinder); + assertTrue(request.requestId > 0); + assertTrue(Objects.equals(sTemplateWifi, request.template)); + long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB + assertEquals(minThresholdInBytes, request.thresholdInBytes); + + HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT); + + // Make sure that the caller binder gets connected + verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt()); + + // modify some number on wifi, and trigger poll event + // not enough traffic to call data usage callback + incrementCurrentTime(HOUR_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 1024L, 1L, 2048L, 2L)); + expectNetworkStatsUidDetail(buildEmptyStats()); + forcePollAndWaitForIdle(); + + // verify service recorded history + assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0); + + // make sure callback has not being called + assertEquals(INVALID_TYPE, latchedHandler.lastMessageType); + + // and bump forward again, with counters going higher. this is + // important, since it will trigger the data usage callback + incrementCurrentTime(DAY_IN_MILLIS); + expectDefaultSettings(); + expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) + .insertEntry(TEST_IFACE, 4096000L, 4L, 8192000L, 8L)); + expectNetworkStatsUidDetail(buildEmptyStats()); + forcePollAndWaitForIdle(); + + // verify service recorded history + assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0); + + + // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED + assertTrue(cv.block(WAIT_TIMEOUT)); + assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.lastMessageType); + cv.close(); + + // Allow binder to disconnect + when(mBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt())).thenReturn(true); + + // Unregister request + mService.unregisterUsageRequest(request); + + // Wait for the caller to ack receipt of CALLBACK_RELEASED + assertTrue(cv.block(WAIT_TIMEOUT)); + assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.lastMessageType); + + // Make sure that the caller binder gets disconnected + verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt()); + } + + @Test + public void testUnregisterUsageCallback_unknown_noop() throws Exception { + String callingPackage = "the.calling.package"; + long thresholdInBytes = 10 * 1024 * 1024; // 10 MB + DataUsageRequest unknownRequest = new DataUsageRequest( + 2 /* requestId */, sTemplateImsi1, thresholdInBytes); + + mService.unregisterUsageRequest(unknownRequest); + } + + @Test + public void testStatsProviderUpdateStats() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Verifies that one requestStatsUpdate will be called during iface update. + provider.expectOnRequestStatsUpdate(0 /* unused */); + + // Create some initial traffic and report to the service. + incrementCurrentTime(HOUR_IN_MILLIS); + final NetworkStats expectedStats = new NetworkStats(0L, 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 128L, 2L, 128L, 2L, 1L)) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, + 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 64L, 1L, 64L, 1L, 1L)); + cb.notifyStatsUpdated(0 /* unused */, expectedStats, expectedStats); + + // Make another empty mutable stats object. This is necessary since the new NetworkStats + // object will be used to compare with the old one in NetworkStatsRecoder, two of them + // cannot be the same object. + expectNetworkStatsUidDetail(buildEmptyStats()); + + forcePollAndWaitForIdle(); + + // Verifies that one requestStatsUpdate and setAlert will be called during polling. + provider.expectOnRequestStatsUpdate(0 /* unused */); + provider.expectOnSetAlert(MB_IN_BYTES); + + // Verifies that service recorded history, does not verify uid tag part. + assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1); + + // Verifies that onStatsUpdated updates the stats accordingly. + final NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(2, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L); + + // Verifies that unregister the callback will remove the provider from service. + cb.unregister(); + forcePollAndWaitForIdle(); + provider.assertNoCallback(); + } + + @Test + public void testDualVilteProviderStats() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + final int subId1 = 1; + final int subId2 = 2; + final NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{ + buildImsState(IMSI_1, subId1, TEST_IFACE), + buildImsState(IMSI_2, subId2, TEST_IFACE2)}; + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Verifies that one requestStatsUpdate will be called during iface update. + provider.expectOnRequestStatsUpdate(0 /* unused */); + + // Create some initial traffic and report to the service. + incrementCurrentTime(HOUR_IN_MILLIS); + final String vtIface1 = NetworkStats.IFACE_VT + subId1; + final String vtIface2 = NetworkStats.IFACE_VT + subId2; + final NetworkStats expectedStats = new NetworkStats(0L, 1) + .addEntry(new NetworkStats.Entry(vtIface1, UID_RED, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 128L, 2L, 128L, 2L, 1L)) + .addEntry(new NetworkStats.Entry(vtIface2, UID_RED, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 64L, 1L, 64L, 1L, 1L)); + cb.notifyStatsUpdated(0 /* unused */, expectedStats, expectedStats); + + // Make another empty mutable stats object. This is necessary since the new NetworkStats + // object will be used to compare with the old one in NetworkStatsRecoder, two of them + // cannot be the same object. + expectNetworkStatsUidDetail(buildEmptyStats()); + + forcePollAndWaitForIdle(); + + // Verifies that one requestStatsUpdate and setAlert will be called during polling. + provider.expectOnRequestStatsUpdate(0 /* unused */); + provider.expectOnSetAlert(MB_IN_BYTES); + + // Verifies that service recorded history, does not verify uid tag part. + assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 1); + + // Verifies that onStatsUpdated updates the stats accordingly. + NetworkStats stats = mSession.getSummaryForAllUid( + sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(1, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L); + + stats = mSession.getSummaryForAllUid( + sTemplateImsi2, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertEquals(1, stats.size()); + assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L); + + // Verifies that unregister the callback will remove the provider from service. + cb.unregister(); + forcePollAndWaitForIdle(); + provider.assertNoCallback(); + } + + @Test + public void testStatsProviderSetAlert() throws Exception { + // Pretend that network comes online. + expectDefaultSettings(); + NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)}; + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Register custom provider and retrieve callback. + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST", provider); + assertNotNull(cb); + + // Simulates alert quota of the provider has been reached. + cb.notifyAlertReached(); + HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT); + + // Verifies that polling is triggered by alert reached. + provider.expectOnRequestStatsUpdate(0 /* unused */); + // Verifies that global alert will be re-armed. + provider.expectOnSetAlert(MB_IN_BYTES); + } + + private void setCombineSubtypeEnabled(boolean enable) { + when(mSettings.getCombineSubtypeEnabled()).thenReturn(enable); + mHandler.post(() -> mContentObserver.onChange(false, Settings.Global + .getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED))); + waitForIdle(); + if (enable) { + verify(mNetworkStatsSubscriptionsMonitor).stop(); + } else { + verify(mNetworkStatsSubscriptionsMonitor).start(); + } + } + + @Test + public void testDynamicWatchForNetworkRatTypeChanges() throws Exception { + // Build 3G template, type unknown template to get stats while network type is unknown + // and type all template to get the sum of all network type stats. + final NetworkTemplate template3g = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS); + final NetworkTemplate templateUnknown = + buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UNKNOWN); + final NetworkTemplate templateAll = + buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL); + final NetworkStateSnapshot[] states = + new NetworkStateSnapshot[]{buildMobile3gState(IMSI_1)}; + + expectNetworkStatsSummary(buildEmptyStats()); + expectNetworkStatsUidDetail(buildEmptyStats()); + + // 3G network comes online. + setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS); + mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 12L, 18L, 14L, 1L, 0L))); + forcePollAndWaitForIdle(); + + // Since CombineSubtypeEnabled is false by default in unit test, the generated traffic + // will be split by RAT type. Verify 3G templates gets stats, while template with unknown + // RAT type gets nothing, and template with NETWORK_TYPE_ALL gets all stats. + assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(templateUnknown, UID_RED, 0L, 0L, 0L, 0L, 0); + assertUidTotal(templateAll, UID_RED, 12L, 18L, 14L, 1L, 0); + + // Stop monitoring data usage per RAT type changes NetworkStatsService records data + // to {@link TelephonyManager#NETWORK_TYPE_UNKNOWN}. + setCombineSubtypeEnabled(true); + + // Call handleOnCollapsedRatTypeChanged manually to simulate the callback fired + // when stopping monitor, this is needed by NetworkStatsService to trigger updateIfaces. + mService.handleOnCollapsedRatTypeChanged(); + HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT); + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + // Append more traffic on existing snapshot. + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 12L + 4L, 18L + 4L, 14L + 3L, 1L + 1L, 0L)) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, + 35L, 29L, 7L, 11L, 1L))); + forcePollAndWaitForIdle(); + + // Verify 3G counters do not increase, while template with unknown RAT type gets new + // traffic and template with NETWORK_TYPE_ALL gets all stats. + assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0); + assertUidTotal(templateUnknown, UID_RED, 4L + 35L, 4L + 29L, 3L + 7L, 1L + 11L, 1); + assertUidTotal(templateAll, UID_RED, 16L + 35L, 22L + 29L, 17L + 7L, 2L + 11L, 1); + + // Start monitoring data usage per RAT type changes and NetworkStatsService records data + // by a granular subtype representative of the actual subtype + setCombineSubtypeEnabled(false); + + mService.handleOnCollapsedRatTypeChanged(); + HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT); + // Create some traffic. + incrementCurrentTime(MINUTE_IN_MILLIS); + // Append more traffic on existing snapshot. + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, + 22L, 26L, 19L, 5L, 0L)) + .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, + 35L, 29L, 7L, 11L, 1L))); + forcePollAndWaitForIdle(); + + // Verify traffic is split by RAT type, no increase on template with unknown RAT type + // and template with NETWORK_TYPE_ALL gets all stats. + assertUidTotal(template3g, UID_RED, 6L + 12L , 4L + 18L, 2L + 14L, 3L + 1L, 0); + assertUidTotal(templateUnknown, UID_RED, 4L + 35L, 4L + 29L, 3L + 7L, 1L + 11L, 1); + assertUidTotal(templateAll, UID_RED, 22L + 35L, 26L + 29L, 19L + 7L, 5L + 11L, 1); + } + + @Test + public void testOperationCount_nonDefault_traffic() throws Exception { + // Pretend mobile network comes online, but wifi is the default network. + expectDefaultSettings(); + NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{ + buildWifiState(true /*isMetered*/, TEST_IFACE2), buildMobile3gState(IMSI_1)}; + expectNetworkStatsUidDetail(buildEmptyStats()); + mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), + new UnderlyingNetworkInfo[0]); + + // Create some traffic on mobile network. + incrementCurrentTime(HOUR_IN_MILLIS); + expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 2L, 1L, 3L, 4L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_YES, 1L, 3L, 2L, 1L, 0L) + .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 5L, 4L, 1L, 4L, 0L)); + // Increment operation count, which must have a specific tag. + mService.incrementOperationCount(UID_RED, 0xF00D, 2); + forcePollAndWaitForIdle(); + + // Verify mobile summary is not changed by the operation count. + final NetworkTemplate templateMobile = + buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL); + final NetworkStats statsMobile = mSession.getSummaryForAllUid( + templateMobile, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertValues(statsMobile, IFACE_ALL, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 3L, 4L, 5L, 5L, 0); + assertValues(statsMobile, IFACE_ALL, UID_RED, SET_ALL, 0xF00D, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 5L, 4L, 1L, 4L, 0); + + // Verify the operation count is blamed onto the default network. + // TODO: Blame onto the default network is not very reasonable. Consider blame onto the + // network that generates the traffic. + final NetworkTemplate templateWifi = buildTemplateWifiWildcard(); + final NetworkStats statsWifi = mSession.getSummaryForAllUid( + templateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true); + assertValues(statsWifi, IFACE_ALL, UID_RED, SET_ALL, 0xF00D, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, 0L, 0L, 0L, 0L, 2); + } + + private static File getBaseDir(File statsDir) { + File baseDir = new File(statsDir, "netstats"); + baseDir.mkdirs(); + return baseDir; + } + + private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets, + long txBytes, long txPackets, int operations) throws Exception { + assertNetworkTotal(template, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes, + txPackets, operations); + } + + private void assertNetworkTotal(NetworkTemplate template, long start, long end, long rxBytes, + long rxPackets, long txBytes, long txPackets, int operations) throws Exception { + // verify history API + final NetworkStatsHistory history = mSession.getHistoryForNetwork(template, FIELD_ALL); + assertValues(history, start, end, rxBytes, rxPackets, txBytes, txPackets, operations); + + // verify summary API + final NetworkStats stats = mSession.getSummaryForNetwork(template, start, end); + assertValues(stats, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL, rxBytes, rxPackets, txBytes, txPackets, operations); + } + + private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long rxPackets, + long txBytes, long txPackets, int operations) throws Exception { + assertUidTotal(template, uid, SET_ALL, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, + rxBytes, rxPackets, txBytes, txPackets, operations); + } + + private void assertUidTotal(NetworkTemplate template, int uid, int set, int metered, + int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, + long txPackets, int operations) throws Exception { + // verify history API + final NetworkStatsHistory history = mSession.getHistoryForUid( + template, uid, set, TAG_NONE, FIELD_ALL); + assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes, + txPackets, operations); + + // verify summary API + final NetworkStats stats = mSession.getSummaryForAllUid( + template, Long.MIN_VALUE, Long.MAX_VALUE, false); + assertValues(stats, IFACE_ALL, uid, set, TAG_NONE, metered, roaming, defaultNetwork, + rxBytes, rxPackets, txBytes, txPackets, operations); + } + + private void expectSystemReady() throws Exception { + expectNetworkStatsSummary(buildEmptyStats()); + } + + private String getActiveIface(NetworkStateSnapshot... states) throws Exception { + if (states == null || states.length == 0 || states[0].getLinkProperties() == null) { + return null; + } + return states[0].getLinkProperties().getInterfaceName(); + } + + private void expectNetworkStatsSummary(NetworkStats summary) throws Exception { + expectNetworkStatsSummaryDev(summary.clone()); + expectNetworkStatsSummaryXt(summary.clone()); + } + + private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception { + when(mStatsFactory.readNetworkStatsSummaryDev()).thenReturn(summary); + } + + private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception { + when(mStatsFactory.readNetworkStatsSummaryXt()).thenReturn(summary); + } + + private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception { + expectNetworkStatsUidDetail(detail, new NetworkStats(0L, 0)); + } + + private void expectNetworkStatsUidDetail(NetworkStats detail, NetworkStats tetherStats) + throws Exception { + when(mStatsFactory.readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL)) + .thenReturn(detail); + + // also include tethering details, since they are folded into UID + when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)).thenReturn(tetherStats); + } + + private void expectDefaultSettings() throws Exception { + expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); + } + + private void expectSettings(long persistBytes, long bucketDuration, long deleteAge) + throws Exception { + when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS); + when(mSettings.getPollDelay()).thenReturn(0L); + when(mSettings.getSampleEnabled()).thenReturn(true); + when(mSettings.getCombineSubtypeEnabled()).thenReturn(false); + + final Config config = new Config(bucketDuration, deleteAge, deleteAge); + when(mSettings.getDevConfig()).thenReturn(config); + when(mSettings.getXtConfig()).thenReturn(config); + when(mSettings.getUidConfig()).thenReturn(config); + when(mSettings.getUidTagConfig()).thenReturn(config); + + when(mSettings.getGlobalAlertBytes(anyLong())).thenReturn(MB_IN_BYTES); + when(mSettings.getDevPersistBytes(anyLong())).thenReturn(MB_IN_BYTES); + when(mSettings.getXtPersistBytes(anyLong())).thenReturn(MB_IN_BYTES); + when(mSettings.getUidPersistBytes(anyLong())).thenReturn(MB_IN_BYTES); + when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES); + } + + private void assertStatsFilesExist(boolean exist) { + final File basePath = new File(mStatsDir, "netstats"); + if (exist) { + assertTrue(basePath.list().length > 0); + } else { + assertTrue(basePath.list().length == 0); + } + } + + private static void assertValues(NetworkStatsHistory stats, long start, long end, long rxBytes, + long rxPackets, long txBytes, long txPackets, int operations) { + final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null); + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + assertEquals("unexpected txPackets", txPackets, entry.txPackets); + assertEquals("unexpected operations", operations, entry.operations); + } + + private static NetworkStateSnapshot buildWifiState() { + return buildWifiState(false, TEST_IFACE); + } + + private static NetworkStateSnapshot buildWifiState(boolean isMetered, @NonNull String iface) { + final LinkProperties prop = new LinkProperties(); + prop.setInterfaceName(iface); + final NetworkCapabilities capabilities = new NetworkCapabilities(); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !isMetered); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); + capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + capabilities.setSSID(TEST_SSID); + return new NetworkStateSnapshot(WIFI_NETWORK, capabilities, prop, null, TYPE_WIFI); + } + + private static NetworkStateSnapshot buildMobile3gState(String subscriberId) { + return buildMobile3gState(subscriberId, false /* isRoaming */); + } + + private static NetworkStateSnapshot buildMobile3gState(String subscriberId, boolean isRoaming) { + final LinkProperties prop = new LinkProperties(); + prop.setInterfaceName(TEST_IFACE); + final NetworkCapabilities capabilities = new NetworkCapabilities(); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming); + capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + return new NetworkStateSnapshot( + MOBILE_NETWORK, capabilities, prop, subscriberId, TYPE_MOBILE); + } + + private NetworkStats buildEmptyStats() { + return new NetworkStats(getElapsedRealtime(), 0); + } + + private static NetworkStateSnapshot buildOemManagedMobileState( + String subscriberId, boolean isRoaming, int[] oemNetCapabilities) { + final LinkProperties prop = new LinkProperties(); + prop.setInterfaceName(TEST_IFACE); + final NetworkCapabilities capabilities = new NetworkCapabilities(); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming); + for (int nc : oemNetCapabilities) { + capabilities.setCapability(nc, true); + } + capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + return new NetworkStateSnapshot(MOBILE_NETWORK, capabilities, prop, subscriberId, + TYPE_MOBILE); + } + + private static NetworkStateSnapshot buildImsState( + String subscriberId, int subId, String ifaceName) { + final LinkProperties prop = new LinkProperties(); + prop.setInterfaceName(ifaceName); + final NetworkCapabilities capabilities = new NetworkCapabilities(); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_IMS, true); + capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + capabilities.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)); + return new NetworkStateSnapshot( + MOBILE_NETWORK, capabilities, prop, subscriberId, TYPE_MOBILE); + } + + private long getElapsedRealtime() { + return mElapsedRealtime; + } + + private long startTimeMillis() { + return TEST_START; + } + + private long currentTimeMillis() { + return startTimeMillis() + mElapsedRealtime; + } + + private void incrementCurrentTime(long duration) { + mElapsedRealtime += duration; + } + + private void forcePollAndWaitForIdle() { + mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL)); + waitForIdle(); + } + + private void waitForIdle() { + HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT); + } + + static class LatchedHandler extends Handler { + private final ConditionVariable mCv; + int lastMessageType = INVALID_TYPE; + + LatchedHandler(Looper looper, ConditionVariable cv) { + super(looper); + mCv = cv; + } + + @Override + public void handleMessage(Message msg) { + lastMessageType = msg.what; + mCv.open(); + super.handleMessage(msg); + } + } +} diff --git a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java new file mode 100644 index 0000000000..6d2c7dc39f --- /dev/null +++ b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java @@ -0,0 +1,386 @@ +/* + * 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.server.net; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.net.NetworkTemplate; +import android.os.test.TestLooper; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import com.android.internal.util.CollectionUtils; +import com.android.server.net.NetworkStatsSubscriptionsMonitor.RatTypeListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +@RunWith(JUnit4.class) +public final class NetworkStatsSubscriptionsMonitorTest { + private static final int TEST_SUBID1 = 3; + private static final int TEST_SUBID2 = 5; + private static final String TEST_IMSI1 = "466921234567890"; + private static final String TEST_IMSI2 = "466920987654321"; + private static final String TEST_IMSI3 = "466929999999999"; + + @Mock private Context mContext; + @Mock private SubscriptionManager mSubscriptionManager; + @Mock private TelephonyManager mTelephonyManager; + @Mock private NetworkStatsSubscriptionsMonitor.Delegate mDelegate; + private final List mTestSubList = new ArrayList<>(); + + private final Executor mExecutor = Executors.newSingleThreadExecutor(); + private NetworkStatsSubscriptionsMonitor mMonitor; + private TestLooper mTestLooper = new TestLooper(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); + + when(mContext.getSystemService(eq(Context.TELEPHONY_SUBSCRIPTION_SERVICE))) + .thenReturn(mSubscriptionManager); + when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE))) + .thenReturn(mTelephonyManager); + + mMonitor = new NetworkStatsSubscriptionsMonitor(mContext, mTestLooper.getLooper(), + mExecutor, mDelegate); + } + + @Test + public void testStartStop() { + // Verify that addOnSubscriptionsChangedListener() is never called before start(). + verify(mSubscriptionManager, never()) + .addOnSubscriptionsChangedListener(mExecutor, mMonitor); + mMonitor.start(); + verify(mSubscriptionManager).addOnSubscriptionsChangedListener(mExecutor, mMonitor); + + // Verify that removeOnSubscriptionsChangedListener() is never called before stop() + verify(mSubscriptionManager, never()).removeOnSubscriptionsChangedListener(mMonitor); + mMonitor.stop(); + verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(mMonitor); + } + + @NonNull + private static int[] convertArrayListToIntArray(@NonNull List arrayList) { + final int[] list = new int[arrayList.size()]; + for (int i = 0; i < arrayList.size(); i++) { + list[i] = arrayList.get(i); + } + return list; + } + + private void setRatTypeForSub(List listeners, + int subId, int type) { + final ServiceState serviceState = mock(ServiceState.class); + when(serviceState.getDataNetworkType()).thenReturn(type); + final RatTypeListener match = CollectionUtils + .find(listeners, it -> it.getSubId() == subId); + if (match == null) { + fail("Could not find listener with subId: " + subId); + } + match.onServiceStateChanged(serviceState); + } + + private void addTestSub(int subId, String subscriberId) { + // add SubId to TestSubList. + if (mTestSubList.contains(subId)) fail("The subscriber list already contains this ID"); + + mTestSubList.add(subId); + + final int[] subList = convertArrayListToIntArray(mTestSubList); + when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList); + when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId); + mMonitor.onSubscriptionsChanged(); + } + + private void updateSubscriberIdForTestSub(int subId, @Nullable final String subscriberId) { + when(mTelephonyManager.getSubscriberId(subId)).thenReturn(subscriberId); + mMonitor.onSubscriptionsChanged(); + } + + private void removeTestSub(int subId) { + // Remove subId from TestSubList. + mTestSubList.removeIf(it -> it == subId); + final int[] subList = convertArrayListToIntArray(mTestSubList); + when(mSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(subList); + mMonitor.onSubscriptionsChanged(); + } + + private void assertRatTypeChangedForSub(String subscriberId, int ratType) { + assertEquals(ratType, mMonitor.getRatTypeForSubscriberId(subscriberId)); + final ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(Integer.class); + // Verify callback with the subscriberId and the RAT type should be as expected. + // It will fail if get a callback with an unexpected RAT type. + verify(mDelegate).onCollapsedRatTypeChanged(eq(subscriberId), typeCaptor.capture()); + final int type = typeCaptor.getValue(); + assertEquals(ratType, type); + } + + private void assertRatTypeNotChangedForSub(String subscriberId, int ratType) { + assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType); + // Should never get callback with any RAT type. + verify(mDelegate, never()).onCollapsedRatTypeChanged(eq(subscriberId), anyInt()); + } + + @Test + public void testSubChangedAndRatTypeChanged() { + final ArgumentCaptor ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + + mMonitor.start(); + // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback + // before changing RAT type. + addTestSub(TEST_SUBID1, TEST_IMSI1); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + + // Insert sim2. + addTestSub(TEST_SUBID2, TEST_IMSI2); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + verify(mTelephonyManager, times(2)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + reset(mDelegate); + + // Set RAT type of sim1 to UMTS. + // Verify RAT type of sim1 after subscription gets onCollapsedRatTypeChanged() callback + // and others remain untouched. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Set RAT type of sim2 to LTE. + // Verify RAT type of sim2 after subscription gets onCollapsedRatTypeChanged() callback + // and others remain untouched. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID2, + TelephonyManager.NETWORK_TYPE_LTE); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_LTE); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Remove sim2 and verify that callbacks are fired and RAT type is correct for sim2. + // while the other two remain untouched. + removeTestSub(TEST_SUBID2); + verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeNotChangedForSub(TEST_IMSI3, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Set RAT type of sim1 to UNKNOWN. Then stop monitoring subscription changes + // and verify that the listener for sim1 is removed. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + mMonitor.stop(); + verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + } + + + @Test + public void test5g() { + mMonitor.start(); + // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback + // before changing RAT type. Also capture listener for later use. + addTestSub(TEST_SUBID1, TEST_IMSI1); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + final ArgumentCaptor ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + final RatTypeListener listener = CollectionUtils + .find(ratTypeListenerCaptor.getAllValues(), it -> it.getSubId() == TEST_SUBID1); + assertNotNull(listener); + + // Set RAT type to 5G NSA (non-standalone) mode, verify the monitor outputs + // NETWORK_TYPE_5G_NSA. + final ServiceState serviceState = mock(ServiceState.class); + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, NetworkTemplate.NETWORK_TYPE_5G_NSA); + reset(mDelegate); + + // Set RAT type to LTE without NR connected, the RAT type should be downgraded to LTE. + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE); + reset(mDelegate); + + // Verify NR connected with other RAT type does not take effect. + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_UMTS); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + reset(mDelegate); + + // Set RAT type to 5G standalone mode, the RAT type should be NR. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_NR); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR); + reset(mDelegate); + + // Set NR state to none in standalone mode does not change anything. + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_NR); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE); + listener.onServiceStateChanged(serviceState); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR); + } + + @Test + public void testSubscriberIdUnavailable() { + final ArgumentCaptor ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + + mMonitor.start(); + // Insert sim1, set subscriberId to null which is normal in SIM PIN locked case. + // Verify RAT type is NETWORK_TYPE_UNKNOWN and service will not perform listener + // registration. + addTestSub(TEST_SUBID1, null); + verify(mTelephonyManager, never()).listen(any(), anyInt()); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + + // Set IMSI for sim1, verify the listener will be registered. + updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + reset(mTelephonyManager); + when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager); + + // Set RAT type of sim1 to UMTS. Verify RAT type of sim1 is changed. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + reset(mDelegate); + + // Set IMSI to null again to simulate somehow IMSI is not available, such as + // modem crash. Verify service should unregister listener. + updateSubscriberIdForTestSub(TEST_SUBID1, null); + verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()), + eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + clearInvocations(mTelephonyManager); + + // Simulate somehow IMSI is back. Verify service will register with + // another listener and fire callback accordingly. + final ArgumentCaptor ratTypeListenerCaptor2 = + ArgumentCaptor.forClass(RatTypeListener.class); + updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + clearInvocations(mTelephonyManager); + + // Set RAT type of sim1 to LTE. Verify RAT type of sim1 still works. + setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_LTE); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE); + reset(mDelegate); + + mMonitor.stop(); + verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor2.getValue()), + eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + } + + /** + * Verify that when IMSI suddenly changed for a given subId, the service will register a new + * listener and unregister the old one, and report changes on updated IMSI. This is for modem + * feature that may be enabled for certain carrier, which changes to use a different IMSI while + * roaming on certain networks for multi-IMSI SIM cards, but the subId stays the same. + */ + @Test + public void testSubscriberIdChanged() { + mMonitor.start(); + // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback + // before changing RAT type. + addTestSub(TEST_SUBID1, TEST_IMSI1); + final ArgumentCaptor ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + + // Set RAT type of sim1 to UMTS. + // Verify RAT type of sim1 changes accordingly. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + reset(mDelegate); + clearInvocations(mTelephonyManager); + + // Simulate IMSI of sim1 changed to IMSI2. Verify the service will register with + // another listener and remove the old one. The RAT type of new IMSI stays at + // NETWORK_TYPE_UNKNOWN until received initial callback from telephony. + final ArgumentCaptor ratTypeListenerCaptor2 = + ArgumentCaptor.forClass(RatTypeListener.class); + updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI2); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()), + eq(PhoneStateListener.LISTEN_NONE)); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN); + reset(mDelegate); + + // Set RAT type of sim1 to UMTS for new listener to simulate the initial callback received + // from telephony after registration. Verify RAT type of sim1 changes with IMSI2 + // accordingly. + setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_UMTS); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UMTS); + reset(mDelegate); + } +} diff --git a/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java new file mode 100644 index 0000000000..ebbc0ef625 --- /dev/null +++ b/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net.ipmemorystore; + +import static org.junit.Assert.assertEquals; + +import android.net.ipmemorystore.NetworkAttributes; +import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.Arrays; + +/** Unit tests for {@link NetworkAttributes}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NetworkAttributesTest { + private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_"; + private static final float EPSILON = 0.0001f; + + // This is running two tests to make sure the total weight is the sum of all weights. To be + // sure this is not fireproof, but you'd kind of need to do it on purpose to pass. + @Test + public void testTotalWeight() throws IllegalAccessException, UnknownHostException { + // Make sure that TOTAL_WEIGHT is equal to the sum of the fields starting with WEIGHT_ + float sum = 0f; + final Field[] fieldList = NetworkAttributes.class.getDeclaredFields(); + for (final Field field : fieldList) { + if (!field.getName().startsWith(WEIGHT_FIELD_NAME_PREFIX)) continue; + field.setAccessible(true); + sum += (float) field.get(null); + } + assertEquals(sum, NetworkAttributes.TOTAL_WEIGHT, EPSILON); + + final IPv6ProvisioningLossQuirk ipv6ProvisioningLossQuirk = + new IPv6ProvisioningLossQuirk(3, System.currentTimeMillis() + 7_200_000); + // Use directly the constructor with all attributes, and make sure that when compared + // to itself the score is a clean 1.0f. + final NetworkAttributes na = + new NetworkAttributes( + (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}), + System.currentTimeMillis() + 7_200_000, + "some hint", + Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}), + Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})), + 98, ipv6ProvisioningLossQuirk); + assertEquals(1.0f, na.getNetworkGroupSamenessConfidence(na), EPSILON); + } +} diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp new file mode 100644 index 0000000000..22a04f5c09 --- /dev/null +++ b/tests/unit/jni/Android.bp @@ -0,0 +1,32 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +cc_library_shared { + name: "libnetworkstatsfactorytestjni", + + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wthread-safety", + ], + + srcs: [ + ":lib_networkStatsFactory_native", + "test_onload.cpp", + ], + + shared_libs: [ + "libbpf_android", + "liblog", + "libnativehelper", + "libnetdbpf", + "libnetdutils", + ], +} diff --git a/tests/unit/jni/test_onload.cpp b/tests/unit/jni/test_onload.cpp new file mode 100644 index 0000000000..5194ddb0d8 --- /dev/null +++ b/tests/unit/jni/test_onload.cpp @@ -0,0 +1,44 @@ +/* + * 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. + */ + +/* + * this is a mini native libaray for NetworkStatsFactoryTest to run properly. It + * load all the native method related to NetworkStatsFactory when test run + */ +#include +#include "jni.h" +#include "utils/Log.h" +#include "utils/misc.h" + +namespace android { +int register_android_server_net_NetworkStatsFactory(JNIEnv* env); +}; + +using namespace android; + +extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) +{ + JNIEnv* env = NULL; + jint result = -1; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + ALOGE("GetEnv failed!"); + return result; + } + ALOG_ASSERT(env, "Could not retrieve the env!"); + register_android_server_net_NetworkStatsFactory(env); + return JNI_VERSION_1_4; +} diff --git a/tests/unit/res/raw/history_v1 b/tests/unit/res/raw/history_v1 new file mode 100644 index 0000000000000000000000000000000000000000..de79491c032e13c800c647e165cc2523db015197 GIT binary patch literal 144 zcmZQzU|?hb1FIQ8iWSH;bko}Y0YZQ30MpvK3J^Md2ZUY}08tClSa=;oGBAW+0aGm6 jPGFir`~ZYzJp!bG=7NBPKZMqv51}{gg3!!9P}&Uuc@hyu literal 0 HcmV?d00001 diff --git a/tests/unit/res/raw/net_dev_typical b/tests/unit/res/raw/net_dev_typical new file mode 100644 index 0000000000..290bf03eb9 --- /dev/null +++ b/tests/unit/res/raw/net_dev_typical @@ -0,0 +1,8 @@ +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed + lo: 8308 116 0 0 0 0 0 0 8308 116 0 0 0 0 0 0 +rmnet0: 1507570 2205 0 0 0 0 0 0 489339 2237 0 0 0 0 0 0 + ifb0: 52454 151 0 151 0 0 0 0 0 0 0 0 0 0 0 0 + ifb1: 52454 151 0 151 0 0 0 0 0 0 0 0 0 0 0 0 + sit0: 0 0 0 0 0 0 0 0 0 0 148 0 0 0 0 0 +ip6tnl0: 0 0 0 0 0 0 0 0 0 0 151 151 0 0 0 0 diff --git a/tests/unit/res/raw/netstats_uid_v4 b/tests/unit/res/raw/netstats_uid_v4 new file mode 100644 index 0000000000000000000000000000000000000000..e75fc1ca5c2e6c01a907792cc88faeddce61f1e3 GIT binary patch literal 156516 zcmeFacR*D~)&Ty_-10g_id~5%pot|ho0w{{>4|Yo*=%+fl=8B^N9s9dbYKkj~#%)a_nt4EXQ7$#~i@z z=}};aDdEiV0}q-K<8CR$4j3#4>@3F`Yr>un23NMw3Si-q;5A!XRPW3xoAhInV|O zV^15nAa1mOu+403x9JQwgd7Q@Aolnc%nbau;jn@)F=|EAQUx1^Sl>2cYeVcrjK7Bw zJ-iva`sPf`>?-I?G3&|rhYmACKW^a{B# z$ev&kMw=OWtS#s*6YA)h1zRh~ge@xtTk^4T_QnELE?Bnh4f#{{Hjiw@?K=*_*xSjh z0qv^~Cnh`DqMNxsCVE!3fufU>vCd%1XHw1I@%l28Xqdb%(>}&_w6lJk$vA*3mSdua z5?6XVS}@av7kh3i%rlDIY=xOjVeD)JZ%f-_amsS;Yx}hotnL4aWi-Eu@uwJJ+p|Hc zQZ4z**YcpZqXloo)e@BDTGFhX8Lsx$Z#vBgT)bFzwwA-jzMb{K{<0T0)^4sZ*)=Ix zVC<2_SbZEQ2Pljk<*+ukw+&eTFvyYgXHK$q^7f&rc9t_DA0-{!r_hBY2n|T-wBSVU=kY=3JDSSTeS9wv0CRNC~4T*gPe6@+t9o z`s&xGM5YFqb5~|!PO^4l1;ZG-NP;8l0F0g8%Sgh4w5|1FZ9r#hJ4oEy0)u7QQx0&c zz-|~ja2sL$(u{8X8s*Va%E+hFTG^|gIKnKk9F?7V5Mz9HGG!(-U|BTS+FOnYSB_W> z|3gffQS5(!`Wza8*lw0!YyU7ha9}c1**BRvGRZj19aMI5AqRwACLr6}){e{)2xBkz zW_{|&EMX^%BSJM}+;%Dnj#!+r9D5CBXhF9GTgMN=*xNr27#bb2;5Z;eJ7T?XNb%!y zmKSSo8{3-WW+&}nId)obMimJeveqLm&6%IsSRaRs)M(hY1Y66QZvmiZTVWird0Jqw zI9tp4n7(W8_(2#;u(cd!{i8tmzP&J5maQ<@*7nxNVwprZTg$P)*kjFnA1h)G_LPJ3 zsXgT|I-oH2)CY01x80^AjGe{^gi+ZEL#tvpZU|!uwzf5^j~U1w7)Q#n*RaZPv(I?R z`RF6|X}C;An(u%XNntS9+HOo=Z~!-4@U%mVF*DGitWdfkUdMBWk@xg#CY8>~%y8Vf6O@z9{C9y6uQLgwd!2 z_4yAtZ!+?cA|s0VUKG;yNrE+^oi1Jjeu6AzYcuQ3+Ou>@P`f{j5wSJ{l41;(EKLO)U(gew@a z%m4n9FGmCe#0?iFFxXm-1;(EG%;!beRgMD-uijV{dl#%7ndGrP_Kw@pCV7piFXYMS>1!C-Az?AuI;>b@Ilk6mGOrjG=*K)G? zA#?0CDZrmnGQ!ywD;3-yPrnFd|*a+j$0@HL$A@r;) z=YON}Y|x`I!`_}pDe^zzEyg&2xdD`wHz!#;-E(um_icq?RB5O5&1Me=a6=F~aAUI| z_K&5HRH~m*lPm6pU`=0;%)8E%UQVS3i-G9V)L_Al6a$01nUO-;S3rUvFW4(zJ1>?W zqIs#Lkn)FGC$vtuI{xZ-yzLXHfdZMXl=`~jjJd=bj%H7s$5UqjfZK0 zkT#4ZLh~NBA;s+kOAE6tn&=Z2@D-Knzqj#?CKQO~&NyglxUU<;<$=cXjgNI_f4(LA z4O)u0^2XF{!;^ZCM1ubieord4h<(C&mso5c@xpC&TDDxi+vDbKBi_f!@#00d-N@8X z;-O(wCT5|C>stn>IV-<=h{kI|>S`Y})tA+?rF1F(%Gmk~hL)2*t{wj6w*Jp(ZcPOklh>%WvBHlrWDIPLap!!MWxQSGM|L5mbzJ(QCt+4zom=3PLO4B*ByOEtvSso(}Q&jaqI^y`a<4iECuIs)r=oGR% zP#7#p7VhpY7B*L>3N^chhN=KqcJ1}-BtuH*i#tzq0Vmz`mburz+M{nQ{sntUguqvba1Grg1 zvnHgABI{p%p2oX}`|csmdE96u!>eT*fwH1Z_yS4VCyMaYW-EPU1Le0XYcMmK)nyafg z@%ARuJo(>( z=2@Kb-7fxlU~J2{onu#ak^WZqoiLy~<+(K&Ho6^|RR4L!xQKs-%O_ke%{e+7U;d}( zIFBeHY#h}ZXO080A74+5|MC>TU7lVy-#b7wZNSJS6GJk4EA%XzT z7o-GEBdwV4FefhC;1vx@G%PdOe?+zsuF}%&Yz!{DFuX@%Zcegx;;TCLSx6Xemdunb zq_)B$M+{>o4>DFr!PQL%2FcZm5Mh6PBcVf?vb{T{Um~x=hrSEC(xLk(383;{uH?%G zwEGLofq;NTstccLN{9M5C=r*5Ly{lP#Okq&p0Yfqw>&uH9l*GiIRj+{J^$C(!2fLK z@Z6S=0Dd{ef~jPyu#6sfM$WMPS{g)d2!+dM2FSsE$-;#?I>p;tos$*xGU`&R+KzCAJYM}DP{(?t$vOKo?^=9NUx)%@sB(zXC zP(&`}k)!`e9ui_MoFwmNN#FU+cfR;&UKX5TB`oro zLSG$AE!cG4Hy{|L4a)90gRvr!us3Xo{!fxZ$M!6^yPcDOk`!|8a8f8Jp>zFZP6}I2 zr3%Blg`6QKWTb916sOIJOj;-O_-|pPP*r3RGCV^|#<#qn_n#D4B6gO_ewnarM)eOGOtA0Pf^J`9&Pq5I#d;xWYz}$q*(OplHh^g2 zVe=2trUbb9l62`P%uNA*k~cZbErLX_Ud`%MSL_q6rjQlCSsw$d3e-VakUYgy2P;EJ zc@0EghtrF0K6XfROi$s{q`?E}z%(lq|EvZ+eORH0|H1UZOu<%!RI3Q^#Cu8t-YSVqA>6kwL$L*pG@?8P zeb~e}<>RS_#gs#uJ!`yZ;;8wAcVGb!`x}fWVPs<30uC9>hXu=p5*N!eE9ZL>AwIJe z20I0fI2i1O`MMMdeBz-wK^apZp&rE*V2W8HIz0eW)o8sO@qXjq{7_uKUam#M=E5HH z4L4b2Rnad>ioIom!pP6Lgeq*fkfg-}3Zi zX<9<4h;pw~`e;N8SxQx?QD2q1>YTvELvx|m-d3qH&^km53zahdYp4cpZ70586#jc)IW~mL><~kc;Nt4~@N`$0{K}7g zW}C3#T{1q}vQOQ*hBVCCI{Wm40Y7d2e#5jNDdv?k+hJ!H9R2;8FD`Z>!*qE*P``&v znKbd0h2w;W{t^iq31Y8%k4y-Dq{Aj5z764r_j4}mAO;Zl>YadUPf^YmDHUVU*>i# zF5;H-e@6AQOT9@D-u{cteWzHrnN~l1?xpgFRxQ~5@SPW1C!C+ZV#>*>=jLu+R5bO{ zUoX5GzK^|iNF49#;yh7n)asqwwbMMmO)KqE7fTL2c4B7k)V8U0Pi`>Odn|V^cB!7a zerADl;>#<23L_cl4Kmro-F1rI*Y~d~9Xp(`mu>mScgz z3MtD`OpUEXotnKRiD z3|4bwx-##k@kYREcN3a9}A?zX*a<%>9GW7tC zADS!>RDfNliGR*o#C@}o4Eja(_vt;~&po7cw2AO9mAKvaVXZJU?z zY5RbVa@sHt!oDuoTDQo}G=jW$zVRECfb zRoZ;gC>4w?7Ag!UBs7iV)O9a#dBQ%%_;eBCWj1q$K{9e&^Bv~P9aiy*1|@y+6ss{G zx#pp0E6jAvYQ7iCPzj?Drz%e~!7w$8jU;MAQ$Z;+Q?pBYd#_+5FJ&eySL+ey{8kFK zNE+N4$nCAneoFHZt9Q(z)IbqhF^ps?wrnFEKi{P;9zxW_IZh&0N8I?(1*e8FeKYWS zwt=V7YZ|<<)A3ylwqlC@#+IOjaT-C~+-|8f2;MK0@acq$CdvVz5KU-7PWkE2?(hF> zSN~_F{hyU{86=@K0q6**?EkDX>9=$o1V2sz2I0W0^09+;;$u-yurQi(MBu>T4pX@yh z<@+F_0!oTuM-|-3mABo1MqlFB2yt=JomoFw#oj#di!P{xTAEcOoFW5ogJu#9W7rZ5 z&hR4wXSI|pzfK*NIo={ilDMnHyhP{su40yceDY&t2#1V~v`bF11P#rvdMvH#^5YMtAU6&JsJ z;h(7TiE9JI{m0eYn}s$5-Uh?JR0 z*R9o3tS<1)C=%dueh*n4?tCa$stFPMcR}|8bJs1~*c9Zfbc3(Ivof z5>Af7n_dsplNI0#KRVB{!knod`A%+eX_c-Eg=iyz|imiqXzQ)7mHd1q43w>HiS zHSp<6edZ`Q>pk=`l8rjMWO}n}hTEYR)=bWvz@{>F<6BOKvCjOl3rHF&FPP~ZamJuT zm8V#Zxx8Y2q=fOXLYVg}NZ-7;rJUF7Uk~-@%};>T6Uop}<4?>QRBwM8P|tvnDo(n% zQi`NYXTh!6%!rxssd=H)Lcd?>;~slX+<9SDjOWQ^khfs@oWR$crUg~C!bQ{M?rm~e zt4QU=&c0rRJ`x7&;fh>|il@hl9S0#S90vce%B#k!_t}#1hgYgw{9R8Rl**$-DqZG9 zyEK1E<;$k!a{h67*LBjIDdhS|^;eJ%J=*ewWAK8f+jQsKx^ig?ISY-_%5jes3+D{0 zgcvA+PB|7k%R|(uMM+jo`0e`(RTJD*qW2r$Hh57=_?SMlMhOb#$aEYLg_1^QNX*5z zp{2erLZ5^a3ko7U=q&=T`F2gI5DNkNOgTroOe+?xe$Ax2j9sL^DHBhfMk_jj z2evdam#+fbnbLBr9Dah?r9go&eN8yIAsQ&(I97{fBBXmqd0jy5PI$%%Wi26U?tr=# z_m|2xF02@pUE5jMT74 zn~{`OtFLL;gWW!L7kto4MJK8CJ2io|8-RL+?b*42j)TNf6smNqDrOAd{MChoZ?&I$ zcJ)@*?xWkG?}R$I_xUZ6TZQgRy3I*a%}RN7 z2V~W|HN8@B!nk2hHP&3WPCQ}gly9n2Nb=ZU`|0N5@E@sHV?&{h>g$awn>Y^|t-6*8 zSZ=uD#AjbxvCOOmeD;+qe;hD#=(C&SPyQ(Bx3mD+(`meV98#QI_K@$GmZyfDHht^Z z6L|lC^DO;Lv^pYgEU*qooW9<9#EjED2P9fH*PKZ`slvC->V2887kL6V#vp+^wp9!x z-be2Lv|}WHRC?1Wc_ruDN`Z1DS?D*FGA|o+JE1K|4VXs`T!noHs&SQ`8o(1GJ~H#j zp%_XG`p`wHikC9K-Tm#}2{G#0x%-h^^=)2)o66PQT|*35fBlu>SvBgD-l}rSnN;aqNaBn~f`0xD-AdB`|}GHOt&6?_)BzyIa`-Vx$o9voq zOm{6Aizy3@E=Ii(rwF7!9ymGEYvd_PzVZ#^oHd~wiH~R*84u_?7)Ld*7<0XsC*COY zw0YQW=~>3?=A0vyZvMV`;I{p1$f%#&T9i+vy~-BlQ%RrSEY~wmLh*3DwB7xZ>E+c_`8130xz(HD zPH!p1?|E;}-2A&naJgN!uq8=+Z#s?br z2@S>*WRvKULORH8WB-^&ti9`EB0L&STwNp=K~@Qp-WBWbdg{?e=pol4h5GBBJG5I} zQpFUHlfn(jB}~{W45(MSlw)JLzAs9TuO@D8(j-ws7eI|3$U1O%+!wGcq#O)Xa{4Z}E1u5ScBqz;w*hij6=pZP=bn9ZOTMr%Vn4`SiH1JkzYTTq}8j(mZj zDglX{oe%tpP?Jn$Sc&9Z5iJD?LYPJOo;>JS^!aBAA1A5M+*`%#pT z2s7Z24-u9b1I-I^*f91w)LW3iuEN8X#zdHV{bVByigOrD^su)advIf7qn+hUr|v^`kDYE#yC2 z++pxTd7I!g&Ws*34kBnyrJpKejOh#oh7i3`W`_?)2@ZW%jA+|ijw9dQIr4impDHS9 zV;iu~$n32TmSZo>YYfJ&JtMpk21~-SY^)D69N3E+wCm=H?Z2la+hFpdB?P ze-9!;R0CKBVTu8H#ve&g=|DW4-do$p3rtn%dl@T-^~?rI-{UO_rOb3~nQHha8u*k3 zTdR(eki9>jgqW&ayfC)8ocHOw)|!}@zkTpMS;^{1vJJr~-xFjTmXWKviF!0)izR&I zBHE}@;pA;kRW0rdw!$FVmS7{ysA3eDEVCf+DR5NQID-~xt+6baSUHa>4qAEFlH`rt z(av%lQfe3%HkWE+D{_;fo!fm;g0R~U0o&RVY{iWYn)&uo-*ha*N_`Z+A;9?rv>;If zi@H!l;+OPd)ZeEOOh<3~Q^}9Ze>=DJGg(W>Hl`W2Ml*Z*HOwwv(`|CuwS*0-8Es& zxHFI4bU!fZ%0nrJ)2|&i+{P)|;7?2Usicn|$q zz28;bD4S7{{Pj!DD$S|3!ni1Mu@wf#h_xuj{W0Fr1A{UL zo?^JBAB-pBV1w( zn1ztOi)JAc$^|3Eex|Q8B)Qbjj1F{v zdWMmlgL`UWoosCbOF8!RH@2H4D9ZsZi_=HIyrto*{yxqIGV3z>m``wa4k$;QQCaP% zaA`-2upJ7m)y|S;QIK{gAGTu@}11s{oKfzV=~k(qsb zW2exfY1MEIOfL8nm1lgI9wyJHq6}m4a?Q^Zv_5&7$Cy$k&l9NZihW79PT+wJ3wwtW zV>RXsl<2#g;wzWkQ|Y)sSFxq}o><3s_+xo*=R-6}?nxoOyOMLPAs z@$t01in_7N?y5WbzpFBx){{12HK@M#ixn_TRF7p!DWJ;J;8<~2xdC09)zH?Yn?kBU zA}Z?SO4C1Rk{|nC0+>j<_Ob6je+VAS)da!ECZSe})P+kpdfd_7SP%`g!Rco~0Foeb zQ2NYDuxLh%|sw97<(kGht2gtyllk{ z+rZjq9GE;x;Ec~sDIM#Bp^zoW<|}!JVptl zepfuo`zqDlyLWFY9@^S&NN;xfN3c?`Pchh74u9DkD`A|>W;4!R`D%4}#BIuBnZ(b{ zwI*gxGxJr>QMcXNPPr?eQaE4bskhdSvi53 z7o5K2=+G4bpHl?ChroL$jvQSepqvg>WR+7adFI9E24PU^<4>NYeuc4QDJIjcb?LWR zG@@fdjZ!2oc8v^}YH6rZe#GV9R0wfIi~FgfBxQSAZ26XUAUVyNu3Lx*N%LK4+nm&NHJ?s>m-W(X-tF9a z(sS}UP2pxH?rC|(zvmtaL}Xv4*PIaiZza& z9)H&DiK(v`7nor7qf^l-bN0cHm$@aqa>o!jcFoLy=NlVf{mf-j(A z5B9lTUbJ~}xpCu+H5;MCF!X-P!;Q|3+P)t|9)ji1?pkE>XnZzh+Lm|r8gD&wc@0$0 zzx3p)Y3or7o&CAz+b$Cydi^3iJpLK2i^o3rrRVG4b(vKPE}t)SGrsCeUVnSSYd#;j zyqpbBzBmhgd}q_c<}j-0uQ3xhCt^;loG7_;*Vz?*kFYXVbThKhG;SifyCC-GS^aQj z0Ltgxcz-=EPHG}2MocW5V4ARZLihN{iQ91PP@~gn)EZfbv#6#-AE7VN_vj+@S2Yp( zeq=ZrotF9_tF`PoWiKq4RTT>}QI2?x*|VB^1@fVEs`nTjEXqN|jgM?lU>UiUq@fUY z9Tuv7R{u^$J%r7QM7xkCwMjgG4z@PR*=~{Ak~qmH5+_#87t`xOHUt8o_M~acfFSMh zyMn)P?ydFJbdR6F1(^KD3Hv~HO9q0TKvc%Ggi*ix(wnzHS27bm4{)a8pR|wGf)D>K z8CU3aNqiI?=G=H{QBL&DlP||e*XNeBK->k*ria!oY}!m#ZWY3FrFDxb4*I2E9vZ)@ zE*2`r=8s)kyZaD?_lA?)9Xk!k#`Q#S_IEbu$yWE?3A@)Z8?S!p(ngPjV3KY);qDU~ zAsmbo%Ld)~+r+6#mh=#)TVou29_xs!Ltlk^jPvXA&IBur^P8FFH#0@f2ssPn%x^S? z$C(_rz2DE?ER1c!nKm7jvbiZBgT{5e-x@$r_WajB{f0fp|oIf_sCkI zIy4AU3a`N4&|T8*^OucRZU@4pxNMh;XV@2nF6X^PcR#PHXM*M&d@*i9v(GML%DAwX zi;H26+wyJDiaMZ6`!UJq{J3jPLi2z+bBp8`q^=1TPG1%G4?}00(BrFVI^NJ`6bN-$&vUUPEVUI!{w-KKl8cA6SSLAE<&PUpY^_B%Ts8bO#Yz`el|#nXQav zYM%LYK=ASLIU(P{L9PKJ(MQy?8DF~>?xFhi(XMbaphJhgudqPf#7w@jIDUe6<{Xu^ zwX7%ek4Y_zTZmr$owyU@%L8#H5$;6Ya9K6bBwY?4KrDsr@udEH=V#$%WZ$fyx-qG- zh^=t(&!KY@a8l(3cz;YUt(7U74IXzYiYc4rA~utM==);zX;|^Z`q>*=5mM|pE~o#a z>d8}VQxmZSCcC|Z$sxy}WM;o-@6Yk35gr`>Q@o%D4c=H)OHfwT%RJ+T6$Q}&RMi(s zV?LymgNbv?`@P|n-(~aVHsyd*F1ndcucFnuf7Oz&m-2?@q9xz+^S+^}+z^?sW8QNy z^2$z^ch8M2fUC(;}Vi-Rk~ z;GZ>jlFkWTGkQo$jJWc+l-UVG2O;{c6{+HhtJr8>4Q0}-O5IvxrEaCGihZ-AQtcx? z`*kaf1Nz4<=zO;8RQq+RUBVsWvyZ5jGGg`m07M<#AB#6THIPX%NYqS5|ESKQB6Mv~ z<<;iwb3nV&^?-I8o#VBG!Z7qo!!Qg!K6TnF+H3kjd7nfyB?h%aFlV0 z=KA#>3F_RI(`;m(TS3IO&uMN#bLoT2)wk+Ub`mQ7DrY`|7xbXD@}ZHn(%4h$(Wt9F zubMY<=D$&v-h6{9b9^e}woWdKDMzC3hHyF_cOH+R@zsbmG~QAZM`O;6PY$f7*Rh!y zH0Gn#6i`I3OLlFe@#V`qXq=m~hsH{2=gzrSnzAkFB#j4~I%u2|c9zCn`?_h&S$k{Y zMS9I8L2ehgUx_SU=1(VBu1aPNgwX3V8DTU|Y+p^|mNn5djyM=gLhAlMi3@)T`LfKXtH(f8GF}Lk<36%ea8&^;B_Z^~7IfqFvY@*j(){b9)f?gkO zJw@ZNh%+>nzesk&Qq8#WG+?bYJuPbsCoy79Dj+d=j9cwO9e^~G^5zJ_a?)q!{AfLd+ZT6OzA zb>AKRol5<#V_N?q&FTSN>uK%odhMPJ-AZ3wMY4YL9p|-?+M72tYYWv!@^s6ZG>s?p zL$`lkdrmsMQSNS$D@%mr3uEfH>4rje@$K5`8tu?AU22%Fb#hyUuJ*bve5+>fIc->? z+BZyhpkCW@OINf}v$av(@25L*Lw9gUd&^I^x>L^HroOsCGZ?MDP@yf2bt!zP=J|b- z_m9mq&DlG}hkiJp~zFGGRf2&WOb0F0iU}fa@$w07eja!Y}*34X|Th%ss@g)fvs!cRwP{Yg`6lz z&wf`a}69*cc6mQ(@J5xD*Wq*>JspY~PPvTjLLjF>t8}uJ3`Jn@I1!u7yB) zD&!e99VG+taCrkl}?~Ds66@!Ex56G#V%wmXcK|1*3$-$a{tm z?()SgXTcWWiFeKPrGFrn^W?uyX)o##LPOo z>CMVvxwHN4-md8d)rN}+)63T3Fq&)}r~1t&mxKex402=2H844^6`nYt$rSW)q+C2t zl8oErV`85s76SG8;#Jrs#$p#ut{%V4tqC%Il>YZs`-G$-IsYQ;>vulzuj(ds=o)hU zyS^#vp4()rQ)wz}Wkap&fZP-))igj>l32ACR@tYuK$gb9A81@%CgeSkjv`|mD%iZ zIEo>c7VWzJp`oAD4T`g?JZ&gg)UHxg>uZ2;) z;wGD|gj~MkIu@7XdX}xDau%27lrq|A@)x&=a@mSYRa`z&+DN(l%Ppn+S8sW5rLWGX zEQ7Crb9sjw<=Ia#aU;9QVpE-5(o9Z<88+=uNBcwChyA||kB8fJWb9e6iftp?S=fJI1a?LMm{uDazTof7HEob~|caKx$GO_eF8&+qRRyKgi zOO-)47`T2GjySDH%Z14TGGF?15dk|{hYMh?&?`;hbvQq4M=ln^i``wykLtOzR&Mum_*1N#F?XTO) z;OuXM^WbnfWTm1tdP0g&Eu;v;1P@D`CZ>sjLZ&=SGG%2s6F(dw45`0=wMwkF-FJeJP6&p=N80a-V zrN^j&Zzf`&Ll9rFu=2g1(LA=fPN%e{^D(Rx1o*_xts=^0v4bZz(Pb{nq-~^QukZch z{hG4Rc4t84k7`q){`+gsLwp{&_V zFB`pdIvi^je;#_t!l<9T*gkbR1*k)2y0;1tf=TB>GVKYbP={Qq8hAq`e^dixz1xB8B7I{&r2^V-Pu zq^4@}wgb=9x0Jy34(|h%hN9FT7B-R7-DA4C=0x5|0V+Eu+5`(Y3kHjf=L{Q* zy&?i9ZCE|A_reo_d6R-R|1dEo6_VA9UA1!TwcA*n{4 z3rW&NuVb$J#)iu~gxBKqZSd4a>4GL|@{l@BxNUr|n^daLj(xjK)9h5E%_B}7g0FZ~ zxU7kn&KXk24!dIZ1%9q2n>GHW>ila$;H1({D9k1$#p=MHTnTVb>VnSU@#h+al4vP^ z4Q$#kY_FVAxo%8jxJN{&rtbpjUL0C#Xu1N?k2Y_X6LxBYGrTVaxa{gy2elgZ|JHAr zxM@&(F2wWdck@>n54E~S#A=rXlHr#&RFK_~;=U6?N1@?*iTcWBbxQR3v}oM_^pOas zFzr<-Nc74i@pK>JMfqdSLD_^x@vNK#HRATkb;e5fFuEA}@=EC9{rIdT2R$EpaGsSiIN zmtokwG%^L~qEzf&k~q6542tFj4@2H#k^frjUHaVu@AL^~ar^rG0%|CM%nUKKM%dd1 zs}Jj1H$nfm>kmm6AEmwg_+SJqj-$)&7pN$Hi5`oC#hONSP%T`c@_3|i zaS;^9lWJkf;w7=LT3_W-E$oIAp;oJ^rSERkr3j0;s9qjP7H@RlG~txfK7EMmX6-8N zwgAG6EsuLpXZLs(kv2aUVKgQ>h;L|KT zvq$tXNj{5xmilaSUc6UY;pr>oNNsK{Vx-Vd^TfQz5<#N+0|7TlgbAQ0YMJU3bLoC{ zgy@{8LZAjM=nzME5cD28ovT)Uh+$TH8$M|6KAL!SapMeXtvhGCZ7=6o}o_PawVUhmo#-FZETe@#T<^Y=8^fOxKTb^T>PMID37~lSXOHQdV%h6-x#eQ23e?uPk5JQI2|U-sB@%xhl-pgSmLYM=@7l z3rm;N5z4I>+*YCdu9dq5D(n`t0^_L=8ghxyMc~W~;rvGESVm5M)cp5j)o`VQ^u|JZ z6YTi>#4nclLgQM^(WRGGLfR({8A5BCQ26n&Plh8%BdP)HcT?%vYWU`dK1-K2fv*$^ zK27lJFPi{wzx0)UP4Aj$2b7T<;ny_V@suh>g8(9-Uw;x&FwmhyHCdVYY<*J+4sFtb zQm!Q^`%xj~Mx>+U3VZUH_dEMh88Cf{Nu@|@Y_{q)U(nNtsSEg`JU(st0^@XC9A+9g zG_q`&fd zn4z$CHA5F*h;= zMd#7$`;GN%<3y8E+~JGiE%mkZ{fMA?8rSYQLgRpIO*Fob1>X~oLc_ey`3n8lq`g;3 z-BHNhN!C9R{`A=tC@&R?Vl=re&bjs4Ro?Y$g*{2StUqThBg>x-PbU{Upf7>6_(~;Z zVr#GvvBUk+I%r%+wI-o3c-+Y&t{cufhnG2R3DRX3l927%ux7}N6;|w*hcAu0H0ZMS zhI7DSQaK>dA9ri5la8euO) zL29XHIb9Qds_>I#-!1!SRjs=Af-(2R*mF7R&NJGM1kI84x{78^;4Wc#x3M$QIndV- zP$VUH$~Ug*VxKrm)sjfvuD#mM4Bh5sn!sT(cDeJGc5>LO@x|l?8GkE&BKsD!bO|{- zq_z#h`7ZG?{b9yH61hT33W2Ca>A&(@l=bV3)za}KeNiFFYZC@eL-GYubQ7W%>`2la z*gZKsQCQcZPF}5V+o`*DLDQM8-hX03NUl?7$drB8$EAeG0~f~CA9l{JBs;scS9&#v zg0&%?W8y-c2JZx8w^e#*n0%_60;dfUbgsKE6`RnZk&F%7~ z*(<7Gccb7N2dAGY+Y24DcRYIO`QGsxgT)oWQin-8vR~>rAe{Jk@S#m_-B?sO@y5i0 zkDB%i>z&Sh(emllNny{Ic^#aw_OspPnv7Z~zeaY(OT`<+{0wR3G4ax(q(nHfo)q1J zn!}K@8Ira`^wf~G(#irUHBAcGA*Jt!qG6$}k7O>al`gyOntuBALz6SsL+o3f4;0aNDgQ+h(IoqXnjMH1O@YvWT?w;d}hhaIn z1<9lorjMC2NvEBWuT#&SVH{_0nwR19Ry>4~F4#ye!o(;3`J3-8{*FG1#1M)Sd8_)M?5bl_E!R=;}_LNVWw%apgZkK1!_x8LVF{5Z&yvOzDvZqB)Jvz4C zSULNYTjK1?ZzudEY}Sq^PQMqipv^VY(=_YYYZ0#d32o6u^ZMdhlpS?XRR1~R|rZXn_oEI07Gb92%W6gxQY|>h6kQ>P4JjX^zw60 z>*vh+f#$`>W=@;_W(;^Od}@|c+zh9u*T7uQC9dNp>1wHG$~B<7y2CWrDU)>?Z@)3} z!!r%Ec+= z0}vk{+0V*we!p2y{B)(UE1+~o_ttf9Zxa-zZ33# zTxX&Y$?aZe2{4=Gg{ zF2``WL}`TJ=7;;GhtXsL?l*nEb(AYQ-2A|2S<7bv-l=~$`cc_cp?5jdEr+U9A^3se zOHi04L@qk~&)y}gAG$p!e@R9Roc(B^R!n%deNJ&DIdu%m=12Yd+LQw?m7RdlG{~8g zau|Xa1ue>+cIKpz^v8zpRbGXoF;MVl+5z5e9(iK&+-*MlU6N+q78~V(SF7CiPRn;6 zl&TE_o(JZHkGc5wYA4l(S~|ydNZThxNf*1J$cxTEQup&iGYbsCVytV3Zim+Aws+GE z%uy#qft$vi2oGqSG=iiXqkTqF6{wvBC!t%?XoPV_nP_AY+|btUJ*aEZ43>Hx^cjFw zXP!wIgK?4y|bI+%Fzl1;fYSEH$eSz>Ym| za~EX)iDs?od-c%U`LCZ?T(o${8^>Qe^cJR#pEPFD#PJhxk1b8e{LXoUb9^6Zo!>LJ zWS%b?RW;+s(dQmx*>f3##D2R9ztXgPub@QP*$M;Ts?+@@7^RPda-ME&gry8^Xbv$e9>jA%O!f1 z?V?JeCzUhRJn6qc>w*`khdQH9ibt8WT`rT#Xd2x?iz%x2-o}+Zk-x2{AAbSpbUG~% z4g0Ak{3OH@lnnyhtVtiyZvillW=cGZmg2odr)T@PTFb$bX5~EUZq14xyEtq@bf7VQ z7o*@~W7u5}EA|XpnivtY)j2E6fR^v+l%6u2L30a|(ssnGMl?gsg%;@8Bv8vw-n?}y z2{ZIiGY>vVZg}8Q%6gI)F2;n4MM24Mqh)$hVGI>#AH>I*LffP#pE&2dU%26P9?{lp zd3kG;*yVP{xu%5NSzMVw*8LEZPePROVV~pbPPi#A$EsjO%z@kWtt=2*Ua)d@XyeK6 z(2THF7?fSEH@li}A9AJ5o=<{Y8Rp6-SNxQ=ZDvKIIqGwEk~MW?(_hd2{%|O)T1K)r z!HF~>Kbd5u2sMFNuzqdX+zXcsYpxlht~srYa*Ina6#GrC3tqUnW$L=DM~61xN|3%t zJqU56itH0wAyo3!M9L|eZfrAsV1d4@n>5@IPWA|gqvh(oBz}iFFIC&WP1|`+d)y!S zk-lFOL3RpJ!Y=B$6)A7g465_AwOCygWxfjD*cz6gY>k)^zxw$<_q6|JvySq!v;S|6 zJNNtH-|uzLg_-|}S;sLC{QFmRzS#_<9OeHRlaO-N$QENz%g_Kd04YsE+|}y;f?ZIB?nd(28%}Jnr6G>N2#BdH5+offk|ESbtuAR3x6Dgi z+Hu3!SMOA?t zxqiux#k-cATT;Qnx{Jrnu>n zaJ)b~cMK9bNcr?Q?==q{?9c{1?Hdm(pSbz+-dpm@$Z@^Vy43~hsw_Ai0;hAN^>0_a z-T3!9@1Rp2;S0DyDCTrMxuvTI&ca4cAiFm&s?^^t}?eUKbuNX`~l%29^?B60a$ zxP-6N`;!nkSZ*1Hi_#r^lm3q0*Whc|zmK#U`V7JHpz)+(uiRts+bfx5lfh)jG&FA@ zjl;stZZX*qD__-bG2ma?S(2GfZp_MjO|>CJEH|AT}lr6=fek#>qOhvr5DUEdU+ zqj2IY@Jsq&ny3+U^F+G46kUTIBg=o20|c`44VnHx2XP^b5WD4b?`XQ0w35UR;+@+4 zARG>8Q`Gq+mHpj$_J~vPmu)|3hnt!``b3gQip72EM3VeMDoOtU@9{5{T!mDU@@fRV z9{hQ$6#slGS+5I+tJGsX94zmolC5uIuHleiNMsOOf0k;_A$0P|U-X8;9s1Cc>6*nY zW9sz-uHlgVc`Au`35$t@bP5m-RrSLDM^e<+za37?O(g5x%j8JNd86IH^SdH*oG`0h zFGRvl9o`Snw=X5q{8MR1n#LrO71~4+@7y%wi170;xlfHpI9&HUqq|{j|8O%2sT8W< z>JN{9?=pQal^k*phZLtsNPZhz?MbKas6VE=DhPTF>LO3Ny_p1()*#D9t^B0?6HKE* z{Tq_}ch`mTB&yxgPv>cPoqj;CqYr+MH?zJ7px-Xz)e8?8-Gsl&FVLs-f3@(O<_&t` z?k2qGLZ7OcuJav1E6eW)kw6n>$~xKkTQZSoR>F9lJ_OWRg7n!>C@vm?r+|KXG4%Mq z8Q4Z**2L9sK-c?y`p_DTWKu32D?P{9v_+w$uoFUirPMv~V4{$di{hq~8tz_x^0_i0 zI9^UXfwHAkPL9iD9ejer=C--Sj|Fn`kKkrS0`{Y$51RnUWf4ML4CGP9s<;i2-tRp}D)yy9} z`v0)^9bj1>|>8?;wIAU7Cs_ zNKrsUKtx4EM3kn2D2f7#|C#rKdlSIFX21G8-|%qX+h*?Exl_)Z`JLbS!Qi82SpVK? zTM`TZKX#asoRo*CZK&6wVM&_0CJMSqosN`-98c*NhPOI@VmXQIMZC0deuD!DbI`xM zi_gGEB#`lxKc^FpS;%lU$ec2kn^SNLw4B5{ZnrDMv=c$=!q?S)l(+iK_i03NChRoo zH1fv(okn3)CwZt)C}e~}oxQx!PwXeoWXU!kogg!Np;Ri8N-6Tb1T4b^jwy`jb8z72 zjyp5y_F#rMPq5M*@t;GF*<*7$DsB!xK6|IPKE3N7Sdm+@6i1^gQ=srS zgKg0HE;%yPvm+C8I*>|Xx`nkevM*5^lQ$vR!a=9Pn2aMBoOOyad<%r@bc#tjJO}As z;a}P${P`cI@Y5I7PNJaWR1|#OJg2hb+fP1Pea9gO`0MO^t}$-n!mV6$Afa)Eo)*)( z+zN)Hg;d~-TXs>)Fl|)k(gxEz8}8JjaZrBZqQ)SUuweM@ZGYGn$NC1NE-M|Kl=i+X zNvDIjNyv13>m^WHXZW=k@Z--i;-{mR!%cV z;^Cl+I_6V#+&qPIK+UPuIE)&nP~&60`O|JbFpFEk7}qY#%fsn>JGV?lpMC3YlgQJ-}I<`ew@@P5wrgecf)^%Mroo$_5Tb2(L^27e54Iwu*{jpYsb58o>fK4!9FDQM)S;~L)=RtK5Hio>FMDGpH#6nY0mjMkqVp|`_Z{|F zx9H|caZwS|=*0{S8oBov>#>5H>+*Jp+Gg&FxogV7U zo07&XZQVubb{|pCeK>R!9UMj%ol$43rmTmpEns&&5Em7*`TJ#eTSdFvZ2BLV=hi@M zIof_2`4)>3B2Dtj47XpDCVQBjK4x4|BFc23i2|&`3sP!CRa*@kJ?Xp{guzllr-Ub) zB@rQ#!VrmT1!~EhTb(Akb81@HtDSD*<6Z`5j;g)Uz;z%vAgmD(N$AV@vy>G(I6{9$sSaoD9!rZQKl6tHLi*# zt&6Pp=_r#M)_v?K6|eXz;id3JC-Ke02E*F4vsC;1%X0!|xg8N+o*SKGRHiv>oh@#c zv`Y?}l%O_IWDoX4RVHj0Roxq?>@1U=g(O0>u}bvCQDt)CVt z?1z*+P?SsUP@alHp022uZZ*rYZMRM^Px&O+Fgltld$|NT8|^?P*ih!O&{Z7FBnLtf zIwA{Z+Nkz=#^;w2HBv&=)!&GZ@Y>PEKxYKh&cpOy^O8 zf=%LQX4#e4_QP#|$oX(^5WdP-aebI79~8DTHAd&Pkp^i!kZW`&58Q~fo%)96FUu`1 zz!z1t>0)y$iy5N*VDi}xKyxhST4sUGYbshFJmF(I3+vgtsK39z$&>l?yu|z|3sX~j zky$EClTa25RxWb9OWO{C#Ytq52qu5CGP9WX<@+J%*AYl0mq`3@&J@!q>c!uhtu(W+ zU#4KYU>CCav<4_^yO}oIVX-$80ivZsOc1INp97(#SQd%?u!|B&WO9=PbPm2ufk60N zh#}j=x3lic92?YdR1Jf-7amS(^ljg--`oJ zxS@sA!c(n9kigHxkKTCXA0REl`mT;9Pxz~cGpu) z^wqbT=<9Pl>H8*k*a4=AZV>L#FVZ{kR;T&Z)d`r4WheGP+dY5Kp^fqs+DAj0S)YC@ z>=Z|%^AHAYtSgfb4QqxS1F47^y|eZ~Fx7}Mv0wJ2nVHh!csOO30J|g@%Laic72QVN zuy5%eEbpDilrM3w4$O%70^>OG)iUHR-2LH+rO}pU%WLdIXBA^Cd^V@>M?fqw`^Y8#Y&@29GSW>-i3z3Oa*J+=#Qo6$Pk(At0t*6q^ z-zgSv1u#+=DxWu+C8O17;}U(1QZowAELapmJ%5N*%~s0gDoXKBjhIqt3^fJ=NG}GI zCqelS`C2`-(rEe81MqGUA!wuD{+n0>)N%{Pm}O|SoOZ>;G?EQ}qeOU?_$$jO>TTMT zp;=bMYz11)Fm3RD0HPUMs5}II@X8-PT!|%eERJKTSEP{21c~rT2!ch}%myJwB<%&u zXx(%-6~D>V{}9uC1Y+u-j?Y)Hl(XX;?Ed zDN_#{owl#gPZXWA$&uarAj9am{-8y_$rXcWg}33Tx<%_rrdew3mr5MRQy%dIIN=T2a+>@ zk_u7B0168cpE^agTmkQH(2*ucws}lDe4`ed?=I-MYMhW(4G#wXmgwy0GrSX$onLA1 zCo+KK3e|6ORd|~UGB~XoVKdadqVLIRk5;%{B(BM0&IMA9Cs}7-$pvp}e>;_bkaGDo zxuFob>t(T*s4KD()ops8sE_%IoW}n@4y33^c--P5I`Q$qkMm=lIA^ELVFw@T#0$IN zTp$cjEB~%ghf!h%sX@vJSejLuWL?H&BnleVAJrYHbX$Bz@bJ~i znMb~QsNYzUyD1-CVmj0vN)g{B(Ga6M_oF0#noDwV((qtv#}ZgIOI$kQXt1w~6er}L zBv~CZysGVgo*Yz@VkcK0mikX$2RM>%a$D3IvgN#Vky-#9bZ1Bj}WcJ){W`%o^#h1wYG45_#@;;D3pf|I^QKeoLMI9WKuQD3h=M)6Yx< ze{*us{}%lWXH52Joy_R=U~K+16^iIT-@Gj4#9g|;9UknJ)b*QY-dKDw-?XyKu&4Ti zEFW>eb@s%DuD{f6@T!y*H7V}jH%>{A1h?t!>^D5oigJ?7E*+H~+HiD(%YL?JROWL? zG+HG(gn2jDW}ZdbJoWEX7?yuj$h-FEj}G$zrWrnIK!&@!t9iRSw(%S zgSo8RJKNvc5)RwjRKd|R69=v0kL&fFlkV=39L=PA%Z>JRs<9nc$FFz$K~}G}cgv!f zoLSq0GicvMzTmOOIIgcUu;>OAld_ z??ceMA+O7(p&0E5p!4EU$h@>SAM6uGMu|?gnRiCRSuY4{fDCVl8ie{Fwj@KGe?U}r zLUMI4J8+We-_Ks3bH5%cxPF62)L5`VBto zTi-c;R2g_x>2pXtSS>x}%+%-01_njt5A;J@Oiyp9O@LeOkm@ZfNwoDnrL6N4rMD=$ zVvPrV#P>QOVs=-Vq9~D$xW-(rGrPGm%DQob*Z098a4HOq<`?3MU0oqdkYyRQd}-Qdp(Je?e$mnSq)HP_IE>rB8_NX({7u0ZvlyCru;7iS(Uqw0g8 zy#c}s(SZC!F1q<~r){Y7g5>o(r5PqIx1i=Fj}X*p*1nsG?}FH?P_QCk;3RUJStU-A zWQccXqAjxyo z*f#yLN$fN?v#ryL4BbpGXrdLzq}OC4ii?)1mN$$mf0eAfWE5}Lt{SrK7uSo@4H~?t z%Dq%YFXSz{^>*rfuchUIaDQo)I83^0X6Y>m4y4<2sG>!AUnlLTg_a<=z_!{Z8+v~1 zVzqq-Y@;$EST-QPDUW+S5S=>66t6kq0zEgNV_`}I>V4hA+RbGUI;{%mLi$>C>X|oaVU&}%)x*aKD*|vUp}>L)j#H&FfwTbvOWN* znB?`vE_iO5T&VZE)z5z+|IpU*KD|(`w*{80GA@Nh-YKOuIXuL$lYg=pS zll91NFXO2h%EfS_E(%*v`NH7hi&apU1eHlJdR=^25?>E{rODX3D$72m9PWOXyCG

      fR5L9&pwwY;iIQzDx&Sr@ULxt~vL&K-DP{yKYVVfT4DH+^|v}Qk`(+Jg!+tF zJzy&jyieqB5=6Qk?)(c1TRW8k6R2Q(!7KwE#()UgLZLp1iSHptIEaOsahGV^iAQa1 zL}RgVY-1%6-^Y`|dz{paxd8Ebp+F?Z&(KNY;|cPmY98hz>ndsga7V}S1Y5X+FUYAH zotTgJIBTN$TtfV0CZh`Zk0L$goAU7KPB|loi8k~_Kt$XimLR=w8#6ZjDr?0I!^?Erm zPAGm~@K5|>tmJ9{VX?s4EP+I(BD*Txyw$i%n$BI;0V@Q=P%DtyD)Nw)labIvD%eV< zYsN|)Ll-s#vBWd8?T}>e+zvbr-Fj7SO(uE zxj=CgX&~tCa~V24&QVxK4)H%n(E=)xy&so~@mRg(NL4udrm5J-IJ5%ifr4+$%Mq^g zEl~Ji7e1(nQ`~aUA`W!Q>9Qv?&NV(52HsEKpS#P}ZB^AW51WzI^)_3d+l}dl-oEo< zutVUl4p&e~voWq{G-8^_okw;L6wG4`#jAi%JZZm>#Jw5V&C z3x-G8Blg)$tT4>1n~B-zMW2=hni3;Jfw4r1f#wW$^(GZz)G?z^F*0P1gt}qI*j?SI zzRzy1W`cx{sZ#qENwBcb_!wISp^}_wPM6sjrBnHGr~QiE(s%-(F_tUv>IH>65-}^G z7@+~8#K;B-#pE+Rl9PxuG#n;WJ?`|R+ZpqDr};Jh+aKAn@ht%PWXXI#C!B(QBGP4I zAI3+n@aqDfsfrs^I9Sv~I?RdmH5_pbUmwl4Rr)`q$deM#zBMMo-UN#pVf3ia&y!50s4S9A^9r~lO``q@duw(u8HWh@IeB!RKbC@ zhiwkn1{9#!mveKeW`E|!r(Jge)BGiW+tDq~DME?1akjBZ%lsO|$?38KJ5W-(aF@3% zF+15PwTMIzceq}Wr-=0$6^ z?=g0LrR{~?Ydagohp(_cyA2Mu%Wicpa^3ywPOo?4f0<>w&-U!{Ys-V-bSmTsLUq2-7n3Vvn+HNy%y3e3}N&Kh9h8awOQEu|v z`?u5EsvwuXm_?;-3NJ!c%2LWxa!g#sdMr9DEwgG1f|1qD?6x$h^@l!#hE>T4yKi012X*TYnOCy8IL5w}NeODwG1zo6+xoG zp_3rEk8_RtuW!Xd2~xaF|hkowNb1w`1)2T2*p zQX^{Ba-jyB_bA0in1?_v!qNj}EW$1@XPB6XF@2#{;UGPX(vXdoqQui!h0;(+=}WO0 zNFO7ANw`2j5%K=pFy#etp2R6bRXocLdpNMqlUx$-WiE&_AWzaL7i?krUWm~YfUD|) z)c%qyZY78r>)}5JaB!>rvUAhJ8j`O=b4-Wf5jIeE!oRzM( zLodB1(IUi}U?>2hv{D9(zot?ppn2A_<+vcH8?Tfkp;36jil(Kj#QOSL1#PAfiNwQ5 z@3)eLSVAVx#sB}MK_@5)CJ|}A_>rf8sT_e#bOFYPk@Ahc`6kH|9f3_`mwU7eFg{bt zL=Vyv9e!~}$DETdzI!juu@|2+j_+}r8+17bUfj?-)o{xnV)+0}jt`FWnmCdDjpKde z8}?4c?8ce#a-41xSMsM~bNhFA+_+(n4~^u2kQ`szRA#w9m!0j4xNx-NH_^YvK=_-h z^J1Vn4sw@OFYhWt>Gyh{sW^Pdkv3Z9sqohu@`5tw3WtazbF$-L((DCKxa^!7C=`H7o?EOXY^s3oS4lO3vZ5rv* zZw1dAVD7wFygZX_S=}Ma)E`+DwkGi9cqX$5?tOO3EDclUzPZglgZRQ#S8GZvlUG%4 zJhHN8xu-f^-KM%y3@%pLboL@wJWA}a^Lj1Kuu~doKD26|MUxl!XuFiDrbn=IE{X3i z-IT5C!VOzq8(h+0S*Xoc2d^J7-a9k=uR#ulCLYTo3LBSrR2k~!KF1LD=MwXtI3rL zxg6Q3kXD~DVugAd?9F7lc)68|!t(@~fxbq?GK_nToY4 zp+smVQqPu4^+d*Ydg29EYFl&cAwnTFW=vVhBFaGWirFfWo|-5nzGzX`8{RhwR=2C1 z+8i>}_OaHUhHY%mj2fA%@Sb{yroz&`@0|m)iWS|9FD$;My~)_uDyrn|>Tc6pnn2}< zaf&3LO_4OsC{{$X_tZ|=l412}`$1U?+bFzeSEWi;4~s4f^9{?U1q%;byUWu}PDpAj zYBf=!OyNG&e!Fyer9sacPt8I7>jr6tSu5fe<{L&TVjb_b!bMpLExe|sjs$5 z)n%N=Mp~U`?+cHQlXIJ$&?(7)D%X9DOEXQrHOop&go@cpiBe`^XCRty zp|P@1*hv*qEv2PJODH4RdgE0htw#4K{J8&`z#(-e@wCZ~;khOn+;AQAIKYG5w+GIy z+4g=~1e7hg8BM0??8RxG^ZSLh8&jtZ5k&tCs7>8$1=nx6y{}R%!TZu$%1+8l=zu)juAM!0WcddrUE|y>v0O; zf~`>8Lh0y#$$kDG#-+d|yHru8lgl$hzxTD<^V%)*15Ro01-wr#n>{V1j5X6_CSt7- zfh5RB3`bXYO6-%gS+Xqqz0xv+z0!mFRhIphwAnOn7=IA`UPt@7uld{R~Z06CREFaB(7szGSn$EtZ>I}^Fv zSVw-2dC#*`slyl9)@nGhruFyP`Lb4jD#}L_8mn!qM{#=<#g~-Msm%VX%A7suO1@ry zfl2ii+V5AX*QfRFp+k1Ceb43>V>PwFyyA$=4bMJmn&{wl0oNs0TI`ZTG)b|d7Jq2) zLC23y5AdZst6bq+djTBx(GDc9JK4OxyV|5S<+JeGbm%{p2bFa@!0pPy*17wuq9~WV zB^L)AgZ_3h(T*g43yKWALB9(2Flp3YuzvOyyA{&(ub3B7CuIi}^_Yp^pk9<|nR&ZN z)x9)b>@2oEtjky0Hp=;~qV*EGKkJM)+A>U+)-ly5(3!XUu*b*>iO(67d0L(4Y}w;U zyVt2M2N_+=rSr4JgZ09Tr!AbTXAJC;G#IsX%{%fyw&j}W{0UN4=g^-cvT{`Ug2Uc)4yi;v^J{4hR!SlAo zI&k5_J1f)Dfv^i6w>x4|bDp_Rr!j@t%7ML|V^pQrZ~+Lo^B*Z-wSiNO?Cj2gOXk>IuR6cO4oX?noQ9qC&A3Ju35i z&OYlnnPD!}H^U5sO1VFtZ`;$glxPMXy&0LgLj&!A!H)Zd%PH*~%tdHsW4uZ+ja94I z6*Ll=LJX;Y3-6i^Tk8l38!-?x^eOwzb+e+`K_4nXKk<1?OPDG>C9X5fnVGb3?@n}n zl_Sxyp1&OcSqq}Yes=Zib@YTWa|h<+YPY;GY#6#Ag(_Qc*mzWZ(#ciq)MgmB(okhqF?{tf}!J2ah_leXjX71S=hkhMaYKAMh)xcYyDw^eAWQpGNIG@>FZ=Qt%+lVU z$>yaqJ9mq=CMqyX`>j6McL-ID&~AzLT~|%Ko9#Sr>hG#odtOmgH5+4=_P#w3u;}11 z%B4)%Fl3RNrk@t0N{o{a9H;9~K<22)m5b)--Gr+B+!5*J#2E>G_D9nhOw}HBP_=)D zK4xpzX%R+ZL|p;sc0Apcu3u3ob=wYZzsdi^_bh5DM_wk0M`S&5%8UJ$n72LXfw(D1 z%<;Bw85Y+hD3aUvn7(~SDN}w-99w}&6;u~Wn50TJv7Rl;fs82XMk174hQ3n}8K!Jb*6%Km zqL{m*ykW;Z zbUGP!`GL=ixhcXMvj(w3o-OGm~7;|UoAIqG|XBq~X{F_YiQ7Db4ll`bXC;NMb zr#~JsCM53%!eLhuWK}}>hR`Y){@$$~y1&KQKRbVD{m@6ZjzdBc9NG!k8Qj%q=s5v* zdLj11teMB8L~9Z}@9J|^uUs1Ad=GA6Zu_x0)6!#6_3KGHnLT=VwKlPus`f&Kr{G+h z(07!{OAw~VQs)}bp6Ka8WSowgEw>aatPHH=^A^wrr%`D2KT3xs<{M0mP1G%ri^<=2qm$sMv9O^u3JbMey+r?|X+}o2 zT59nT#mBJ_fO)?&P(RFAmJUDcfDb>xc)D1Go#di7k4po<2E=wEx!Lli>n&%`TRm@y z+I+g>(r15IwtC|XLsn`mH#0RNJn%y>46<+S&Vzh;E`0V}9dbuGC=|Y^rU?un%2V8; zcA0%pbKfL?UZ+X!+)jh6=VD*q{Z;lmJ!OOqK3jPEr5ag{CU8Y@2JE$O{Zr)fs*R!2 z5$rTO2I4llzLBu!?(@>+-G&u&uRqiMZi+XQExoaHk3-X^ew%V%zo!kE zR<@$e>GXzs&QMhV9UId&ItRhlHHVE_)I?vDVwg8C^`!$ZUh1dL6*Iw+Ojk3VYng+6 z+AX@#jJBGMY!2B)ox21VBcRBWi7KO$&(Q_PQFJtn+yzfE^N%uDnu!!v8Z+t4xvOSZ z8CWvYS> zgTX=|vlPlq=Pg)oG0Xb-g^N^{R;w1T{oS(V@6XgQQmxFu+M)|8&_1*isa(Of8KjO4 z@Y`2TqitvpX3P5WGDTE6QG{KN1{On$M0%=~QY6M?BvPbc7~0rCD3lmaGoEFjRw^(< ziPV0U&_ID*{OVa4TiC3Ts^r*OkHYI@LKTbGXM_T=1*30P%KPVHP8ziHbqPQ5qu9ywB*80nEE zKjw;IwaJk23F8KWh+y zTdJ;oeoxp*GjUU1AjiI_l+pZexJ z-W5!+cxsG9W0qM5*env6vxY}5KJdC`6=Pque~ed#7IRkckhd6Y;@$-$DAkYzd1 zDPc5=iKRRpn$RiXO`E{o({uX7qN1m_fvM8RnV|BW` z*W#RIkmZmx+Cp&UQ-Pb}f@RN~d>cWlZSb_6HdhTUJ~c#Y$h=rFkm8$BsI@sEF07uSYRrV zDewaJTpNrSkSGi({A4_@HxwWVUOC4HGL0@9*a0)tbE^D8YIR@)`_TbMa#{l$p9C274UW?chr!a7<}9KIx3O9**nIbXOl}1lC5|HkfpXD;`3slMe;7zotMMyf^!CH>m`P9R3Ho6| z6L)@&Bfy{Ke|@S2iiufzc;;sj+b@0?{l}jKoV|xm){xN>c9XgUF{D{?-UTytIw`E; z;xtj1OX6X8+@k;FVT}LJW66EGDl>+FC;Q}o26?LP0{(r>r%vge7bh`Qm=ZBLRRnx~ z26?-mmA5Cv|IQ$pP;qk5W0nC&tBT{0{5?q=_?dA?pZK`XA$I2D;W6WIjcY8@W5(gc z#h(%93BM`VM?T?mZ1Me*p|8i1ne1mu5qz?n$IdM#!uT2%EWLKOj!ua_o{a8A;C}WZNFE^gvB)?Oxqfc`0_$giGgGmhe z$8VVA_u*jspFKe9WbmJnH^F=^*N zr*v*I$-U#Jba9@vzD+gX8BcVQU#`AQ;)`vPd&f`d>JAwso}{dG-v`Gb;c>sI?tL^9 z=WkpplWw_s{e;gUc_V|-le`bf8$4m&NPUARENfjI9edTsEjL}9`Bu7Ej~ho<*6JrK z>#5v|k7akVA5K^pyYjesBNk2L;Ys5Boc04xSO!F3deY~R?*mWxeROfg3gI!!;Ne7v zM8k{9lTCCm4fK?g#j&SN_A{ldEFV)XacVhE4Z?wl{ev%KRR#wK=GG)R5CsS3<-oa| zwMb-pE4B)`85pIb!;x>j!vvfVbqkO8&FU>zjRu$uUVA&+RalWlZAmmfc=N6H@((gI zKF7I~H(XMj%3v=rHv=fQnZvbk*;AA}%4U^AWc{ovuSEq|wOaK@a$^~a0Qg9@x(xDR zaR^(?I;QgNz@3AV0f%JvQ7Y8c5IsrcRCGmHaLDy+@Xoe*1zVyOg zYDb~4jctu)?%Y$hnOu8HAgI*lzHmNTv`g>whDvR3nX-~tY8uSwM6D1DOaCeOJ_$*2 zu?vk|ffWlquo@a)?wDSA8I2x>{SHZn9>4pd|C5wX@Hadw`e&#xTfS|6@*5+PK&DV0 zdl<6G=Q5ZRN8N`XriTIilrqWvAuH56?mdx_5%VwHjb>61j!h&D;5s?-qsaZXi9D$A30BSa7$5Bz*E&RqNA zS{5Hrq-$R|3WJ-_#bNut5$Z;u>{2Gl40EzsEZBU^^nM*tN;9`XKuH@&j+n&jV86*- zi3tnHQ7<-+RVC_n@gWH_rg6WzEk7T;(?8wilq*jtw!e*2nXHVEGlz`Sy*0iE?SCV@ z=6$jw$gU!9g9IHU9y%=A*+hyPQJK0QyvwP1;AiB7L?(g%#%QeM$0^&8&$*9tBZ*jn z;>^hBLUy{0G~f!34`%ANWHZ(Wb8-tmMT;@?Xls8`&2(q3y>tC3*P^*M=Xz4kMLlQb z%C~Rj+u(66nyaU`vIcN%&FUP)+wQnwyiGiHA8%WHx8U~9wR+BYfwRObI2-n`AIPYR z#rvHPw&5+u9>jA z(Gd#wfEU_RP|oR3BCYjwk zuPwlRNPj3%16TwYr+k6ELk=#pN}>HTQY-B& zJ!>N==_o83HO;Tat_rpfpW9$~QoF+>MVq3{ksMUj%FZvj@T)MBQBB83?XuygxBa2S?y70ku?en`OLW{b7d+cg+h#07vn}K&YXuEr?}egdM^RjSgy|V z6xq&vyfcU4xl-7O@8o;X(g$^)XnV{ZWET*O9Qp#p}IAn>L%+shaazdie8c=#>zkq@^} zUL$|Jb-*9|`xDWbEmTjOH1ha&6(K}!+LbV;=N80oMXdufPBf{D1L|Rmzd=ckIN|Qn zR!?EUp83N;FJ#|MhoO)IfCuiOSiobq*O5RN2)D26)cBsDal380Yq{$|3LGz&1?;A4 zFB!C!dqH@MACCF%Yl7VZ;B_CXw;FoFccQ__Ds<`0ebiaCi#ie!OSPWPp-$v}eq!g_N8Lv-bC1Ek1dG}?al6>C z*L*i}-I&<{gRiyMFe%Wbf7CwGtRV$OTV6G)qL*i)#2Pv-jM^C}Ei5M$JwEJE%s-!lQIbF$SypVsOx1$bTWpVKJ= zeWN)WmWc!^Y+8We`#G?o3uS;8=3m+e^MA1wzJ4wNp6Z2*a23ko)ir-r zTae*ogP%blv|mK~-5|grqMlluK^47mNA78VM8BJAMw<>p(uZg7Q?4g49d*3;n!^E2 zku1%A)8dpjuB1SH2i+G47oam1x_3e45$LUiJLOc)AUvSn*(z!g9j<|pNci>QU58Lp zFq?|g$ft4jYYt!z9YU!mpmA<_Dt$ASc4>pHCsBM8O8a6>04k&z(x&zAN1-M~CS5vcU-ryud@M6O8!zBnGpuEY2NCfYI4X~&7AiAQXmLSyVAf5aA! zjnZ6?%gN*1Sk3iVT<@YAOL1)BG0zLwNRDkj!<=gTDnI%4`YOk=?r+GILzsQk>2?*e z$Bxaa0*JsO^acTX3Ep^}>|ZMZfz$m!5FGx)j^C3LZusJQ!K*Kk{XesU4YD^kjEOh+ zj4trADId!w*R6A61i#bdPXIXKfD;J#LcouQd^vfQGcBLsp}ss$h$m)P>hCPkq`R16 z!M~(L7}4?u9=8u({vPK=9-eL{IO!@ms^3IyrLqr+{ppe@ncze)D!2eT>LqzKC2$1| zLYINb1;J`Vl6kprHR`4UXGN^r{Hc#WeOm1ELlu<2mcTZW;*npZL|>aw)OBDzfLW(c zJ`@>S`C$f;!bpW6fnOfnnMt>tPHF7YW5)Se{_H%{2&21MS3!SqEm=;ktc3 zHh=Iq_T@F_v#_`2Yp(L=?vNfoS`Eob zui(M0q%53NLCZ*VGU$cw2TTGRQ^6)zYrMLY0Xfo$_-9LFMS+(WSNjT+%Eb>(V`l@I zXM`OEwjGULH$hsd4en_3Fp zWPj3kh7)utHbL+cHG_-jyLj}m{GTV-X4K8pLaF6jBZeNZT`~fjuSkE>rwJxPwPiM) zQy~xk%2ig0QK7!f(L|J*hxXp1u3dy{DWU`GGcaq!P829g$$?wD#Tngn<~d320o3)t zqSh($fTrOpgxr@!bi>V57(K2E{wi;;{=hM^xNE5>mAitSCtPKYqXN{%UV~iG4;Rs2 zO}^bDETAIU9*D61bhmaVcuP)zHIn{WwFrsVW{< z<@nW%KW&<-QcYc*3`k|d@*1v553AAMv--Fi;X1_imD}1~Aui@y=hMG?SY=3k@$6LM z`r_J3rq~91-BH5xx8Leow{yu6X@}vpZ_g=DK6U2TF#%|sCu%wfA=1!4G&+nNgl&p@ z2f&l`bOy07(_#8Fp<<=b=BWs*+4@TOyz!T!&xsnspd$}r=6WwopFKPyhD}opKUE>h zu{-_zwj8*W2$%kJk=^^^X_K>?^Tolxtso2b2DPw!#lpoR&3cK$YscUa_=D(8D}Ru< zf*HAt|0fHN@J6VU?QtCN4tNa$hkt=g(N%gWGye!PoeU-SQ)kmAZ;cs1mk> zO`zJu_oxK?S1F1jZzLryWGOL&Aw0UnebPO2f@p`xlg^S|7e>>*bdRJ|cSTH#M6^tZ zxi55wIPSl~c)94#@Za%Yw@la@pGQt8OUju)r`NqLj!a$f*#n)2t^%jFqrzrfdT}#3 zuh5bQQca3dEvaafQcLp3WkOf>ldbNL%SL=XFV|mBXgoMEAt~dVZ{_wHP&P1hi6&dQWi5cGL?D@D| zd^jZ(bu8J*ex`(PPImLyxf!|)JRv@1yzEID{P%m*b3Vrx$!8>i9|t&X9X<=Y>fRtf zu|#A9qOSut-R?s~k}Wwm-wJPkoQBYuufyQ?9XxT&SvRxCF@-y}G2ZENtn|VX87GeU zc9X7EC`fxtFhF}RaQS@$shL8&yhJ)LgY)(hWs;Cm0lr3Z$xTJ@ zj(`IqO;VVwGp7dPJ@PMVKO7_eYM2$7>8t#C!N(7jz$3FiD<(Qyk)(pe^A$^0QS}=DJOp zuFT0J$Tyo3h=P^A5m{K_rIU>DjToW-JzjkvdtRGH=aFBnPWOADE-AP_;}dnqZQOMvYZ8UKPVPQn*5KwTT?uxC*X#vi~QtZ)HK=nkq8h zY1%+mGbnRYBD-_-e@fVz>PKV+TnY1Mh+O^QE9SWT@ReoUY>`*H#?-_*GI^W@)s3q% zqRz@4TEop2(+_;+aedW|yUN(%XODze!M;ES3qh@())D2H5INzyJLgHRRrcZEos5Di z;UQlXpx${5f|lMj2)YgFvPN{9P8T|@CpUqDpc74E0p0yPkc_WRj-l_F)GxA z8J(3kP!-Yy(~S?PVnv9ay)KSGUv27OVky#2$Alm%T|ZB{rk@0E*=Ot|SBxB_)xepX zYUD*P$73OJcbB zWAhS$4Sl{evhNiv*Ffv+)?S9m2Z`nK<I zUxD0BwmRNvMs>Di#i{T8R_hAsQtiYsRBostx7W7B%r&}b$!hOHbz?E&n*)57v9 zR1iw}U!(F;7_Sy4{Q&D6%X-{nZxo_^N1&&GcIsQPmkfN4Vu$yXuge$evCQw}|3qen zrlzvl(`Uc=*FaDk|6Q$+no5O8f%ZW>=D9CL-q<+I6SB}{^p7Ywic!rWXg@X(Y(Rf` zy$+C>F2&2H=UpHw4bEBBd|qp~^`pvp!|D$8z7Fu$4&H>d0gG#mqUZWAJ79Qfe&0t$ zUzVrA?Hdqv6NTGtT@nH!3 zAWy^@)tH*eW-VIu3oqCUN^@V(Qz$K@EXW}fbEx#8AgUC#pj5bqFe4Zgz+EUn$B+l* zO6|l-^^aWaff*Tu${L|b)Bg9XQFs8lLo}FjF(VO+h3qrz?4?`;e3Bn@ z#t-8s_yhj%H99j#YAP-q2OS3C{w;_2%xu7`rWmcnsdMr8)Oarn zj_(u2xKnuQS9IGV{P;|)(mj0F{Cee#Z3P>$YL(SJpZ6V?ri{K;6Rzy!M0^!-%2erC*yT@iHdJ-SU9rW~GmAXgNoY}ieQDTii` znhwuAbd)`RQG09St&M%_B6cy?;?c#*XDTFN$~24NnYT6$&y2Ysxoy!oKVn7kDQV## zRrgDx5OJ_qQHuo3FWwWY-~Lj_igy6}0V}o@|D~1IXcXVs9FU4xnOGw}404&W65cf; zt=_?J^(muD+Vd@^eu(_E=JR^#LF4{ULVtHz(j=*u;$`4$NpmvZ)Jway^-{OZVLnWP zHbJ{j8Q}~a`tJ4eR&ln((;!89*|1)E^Miv1p?a4Mc7#Cv#}}=2niPj+#`HUg{pOEy zqpv-%>?bb)aR`Xzi{-vBN}HJI1%vrsnNqALGnUtZzQ&*qeq}+bwgdiuf}raM@% z%)$9#KbRUd4c`IIM|3J4E3)5D`UD_?sjd$s?Rc^t9iN^m%pmRfN#nf8OO4--d8dE6 zJEot(SS)>HFA{+)grfEdCFc(fJcB{>st6FP(F0e-0Hj0~I``}-BDjs3ynLwK(us(89aa{n!);{m3`ZxM z_vpmy{2j4B!EKzqaT~@#8;j5DwQkIM>!^k2x(8N=zd7P~2us#IH{qsr(IEJ*NU}R* zS@=w!mAmQC8)r6#|0Bi~`IMnzqf09*FT+XegKN%C8%jYT2ZcG|?5!j=t(V^2N$+Sz z9x+UIKjV{xj`>qbm#E|-IwXMcOJah~Fe$!l>XL~XoCUl4l5A7yCNHO zn(oD+rWe1v^PZ1YzJ0Bs#|L-j68YJ$5Hq1Cl!4!(L~V*;=h9%qNNv^!r+#(KA3Cl< z*i{ri?b4#meyFg#Zgs$LC>)L_F(ucS0T1@dO?u0I`rrxF>&`S(Gk%FE=sL>VN}u0J z4+Sz^S@77&Q`!^3fmTp%MDP7qgHQqB`{X3oT$u+CLd5enuG6^KwGxllCJ| z(qBzwIz^4C{dfpx{2EkU-#0o4GTG0+B^}9<`x3F4N2J%YdfgeXQ);03sdc4kwHoL>_RE>EdX&RVZZXd`6 z66Gr0+LFY{3+1?@>t-fd(^_nrZ8GFrNGVAW<%0AD28Ii{yYQ)pVMv4WaGeFp&L$^ShvF zp3Cxp#Y1$hw8vnGE?m6-r5rr-THSCS6Pe|_ob_VxDG1#Qjqd39+o^xXgTnC_4j3K# zSTIjmQH;!e24{3vr#(F@BpB73C?Po{A zKB@jJslw>RHEN9+X275fOkcKIXKrCR?ez~Wm#oz)7OYYj%y~BqZ0zS4YSiZD%Sc0Y z^flY1#MnJaL!dkU z<`f+>PJqQ-GS+D0=7O~V4~HoBy0zf2mEfi-n}P{?3!e%+ zq;lt_*ujYmciy^z=1ENo?QqKDf&UKU=O+1m_?#yh&}NJ!M4rKa4n1b2$E|B{bDeQK z2#uGQ@N@#>lsF^?9c15WcNwO!MO2aDo7YNH{{O>`Dnul*cRvUrBLk{ zFbsFn6MBhj4wJX<)6t75;*_Ln!2EkuMFYA9P{x728tlN&mgjjbz z_h4h*mRrIyWz5^fYU^%VRlYJ%0TU-d(CZ3mqO)%XEbw;5YJ?tMaEhpc>Dr;~ukLrkbY^E4Qly7mN&uYW>1`g4nFs2 zTyHODBQ#l{Mwmm9znEzygjJY1E1_v4T?}dxqrXc$>xr;x zN&OaI?Ie%yiE&Wgtw4yxW+hqYzw;ZPWN|}c9AuwwPu*#cLual8LT-B#YaBGx9>p@5 z4tu$YD%QJ+6LdJ1Gw+8L2fA)$E{I2vD2k*Di zQ8W&9SCb0Wp9jOGS9+K43yw!sOq1x)J1uJ0;-wFmF?3oPu68?$O7(!&HEoUP@e4W6 zREffvR4SD|I0O%O^_HMK&&tK|zqqy$+1PjwzLH_kiH*Xv@B8aklRBH;jMhw|3>|!uDu5zkGd@ zcBabehE*;#qM5fT)Tqk^^NUV1j#`b-v*EGfxYhAZiP{m`y-WjqWhHP|J=DDQVJPyY zYT=%7(9hzGuPH*&q$ebc2Bo!IeO~Z{v(WYnM>@|aUgnQF-ng_rg!ZsLziaYWWs6w+ za4!ht*r+Fl0i?eIUO?_EoM{FhkpANgX4P$DLE>Kg(Eq{2VJNdi)_KZ3*fxS$oPo@X z4bmiyHMk?WNR-xSM#OTc=ycJL^px-vi&Zf=Dj_*X)Lro=M`-QY_5}G4XziyfZ_UJK zcwBDWzZ~)56BP(WjSUwcLT`Pq3~Qxh_)AZIR%^Lr@RO%o8Ur zwq^a(%B2cDR~W>C{Ht(Is314t;$@Qpm$)}I%dAgdFw4>MGhQWsSL9XyYms}qv#bbs zqI?LLUck*Di%?cTK0}CNh3e#GlDEZ1XP@nD;q!APPJMFfEPt%?QJ-WvKl5W$J|&X; z69F1_9)Jf1y@VVBmaBH1u&RI%h2$|g-CkTEi9dF(X4c&DQ(QgYIr?ypHjE9Ylfaim zb2#}lCBtBS17fq|#bB*{j+kM>p1=HJy}~UFx+BP^#^~xYYrY_b(+iyq+L+1h9U)|Z zjHw~B&5bSLUnETn9oZlJ=S4Q$7dSn02+N*1=_8tymzR>eAR)n5k6Mg;BQ)J(mzqTo=x8sTGCI{(S*9ZfN;kHwx_eo6;xv7u zmKbyhtquztWsjJ>=MIs55l5Vqih!(cp&)>1U;;Gu=8<)Rqh-lNV8WJgM$()XDBS5; za}ITxAIdc6_CM<#xXL+qtEaU2Ny@1h!p{1>#XoS8!Uy<#wN1|6U>e|y$8SG7Qpey| z6OGBxB4PHzL)X#uP6Bnsjdw*O>(0iYhxxKs1wrU>A6Wt7^B~$5f|rcwdGag$0hJ?~ z_;udeeVZHU>g}ZJGBEp(;pMC(vR<;@r_!A0@AL(;9G`3hXT z!w+ste$SD9lDgpQyw3QKr}+bS3!D-hr>@HfwsVQHXDZ=x%4?kBHm7aEg9DtqB0Lh! zYDKcfkne_D3JkH&WUan`QEehfXCzE?EBuww$ICBO{ONJd;)!V8x(6CJ?Ufv)7;-1% zv1Th0lr0rh6kAnSGoi|wB@;^yD}6R(=@Bj1qFr&~cJApkX@N|TEwlw84uJS`l~D2w zMQ~)?L8rVLJMoKwM?<0nAIQUcsa3L6Y38a9b)v7@Xwi?_se^?7q$vai^+*_qRZZ0- zf76nx?V&llFd{*mnb_AdUCp23>AG2tb2{t&Avj>C88@f!Yz=p+8ZrqYOWo^dQr+O3 zB`JTifB-H@=owR*-xB*fu`} zH6!=o?liyq%oj~sL~b)rw2?rc+2(pC^zlz4*MSMij+&BuDar8Poa`5TZmwiW2+Ha7 zBPahSd8i@xnf(D-!3{Zk15;_-HSC2zzCjkdfAVm%b$~CLEBzEA>j!=DC&YZpb1LVV zmh(|Jm&}Q?ZurkiiTE<&e@An8HgIg7q`afL9u3|ij7DEOu72b8A9^ffBOt8*<=Fg- z(A4M+A%V4U>ddBlo(J(R*iuKG%3gcH>D7v8MWGwh5-e^VU6$Y41MWvRWDFWzbK6qh z{?3ti(~>k#H*d1bR|M7SVi;akZ+U%e4W3|gffbI6v!e?}jZ&cm+HB#vBl6N~Gw{+p zt9g4{3G|>+Dpr+2-4nT(VZ$X+0v#khuO|0+znxmlQIm~CFgcnD+Y0VYgM z@TpZ3y(bm&<+os5vd2vlDc#&C&ZIKXmOVF>?SwOn9ftuZktV?Q-EuXF+ybkPD1chb zkT7Kc*bEhnG2;Ob=treF9h*F*^_+?c)Rd_hXE^l)bL24XDa^5F)iJZCS8?x@fgWb0 z2q-%@Fl#7fB z+}WI5P6|)@n9qG#9_KvW!LZc%EN#$~+mq=TM=6)Z;jsiSL{7_+$Sflnp)OIcGC-Xi z)!1LSG{v9AmE__0&reRyk)9~Ckn{6tgU#6IeIL?hKKFTf>hgQ|_+!V$&g(2}Ml?kU zP5YNX5q`3`Ja%5$yO0u|O@7XDH27HAj~xRUH(pFLEPmL|XV0dL98K7>{QJNYXC2_r zt}_heruHv)@slDh{FIP9tTzj?>`CI^m8+RG|KDcrvxwZDl|KCVv3<-N@_e6%G@VZy zGH*l<&>VakHCR_S8b7o`Fmz{755=-E!Rfl!?b<~wbeMZKKrE)6(t9!tU zudlaM-)^XhXv1e2k=Zf|tz4r1?Ku{sHSQ8-D?3r~84%>;QkNqs?`ptSo`Z>S`%PO# zS`bXwK;Y#Ze_&Fms4onN{w$SxVzyif50JQfAcGUlHwb?d}m=>|FicQCH6kkwfx07 zT{p{-W8Ke_p861XG~4G0HmqEioV|c2A0Jq`EReB)4cVZ9(AMM(Ymq%$ANbr{iTn8G zIVX#7Z?gq=nX#dqXQe9y4nP}=77>QXlc7$J>Bmm%r&EndyZN-4?|sPQ&1TrCZNqV! z=7Kw%0G{u>2|F; zr4zZLTc^PCgrsFz-M0D!s_5h9Ercl5G#{M zfV(8U_`cr(=r>|+g6Z!wt%I3nW@GSc{8tMtJRliy> z;k7I7$R>y4mkMm{A?{EX)U+D}*!_&Z*jw**fPWsL-tjh>H2m|xLIqyzhw?Tx*~syF zkLqNeEP4GJ#{R4E)wN%oiGsou*mrARo}rc7`nu0D9qIZ|7+N0m_f{|TP@26c_-_v2 zb6{VVAH>9w8Po@BP0MGup@mXVEUtx3TGl@o$*n14wB}iIc5C#{Q}x_mY~NG)k)U{e zw$-S_etFfK$^F#m-)f$}|4mpm+?8aTgdB7iyX>;^{C%Yt*XJ75l8svh3ssEviV4_h z^xR(<^(`VR*i29|eSwg|dn6%%fYPK9Eg`-gkw6Vsa2v}k6bSorXV2ygtTX%NY%NE* zV~XluaZofxxj&&GG0o1Az38m)EQ{c=e(_M8_z0mO5G&Q?usoLP#(k10;IG3U!wra8 za?I=qOZ98%Ehg=c=mzLrUO`IwhJ-=Gb;!++#{6XFEK)Fw${X%=uEbIDv&_$24RHOF zHvjtkPvs!6TOJEfNEeGp|LP_r|G^Zp6|-j?@i)&lM7lPe_^)cZZk8iQ>`$Hev2DoI z3sf^-*EBgXUxIqNKQdwS9mBuAmZ?XX?Z7RwS;jrv#Zz@p+WeIJ%;$N?zB8TpbHLbe zdI>YZ{j^2`gITFq!d0Cw$@E*ssUt9X+D$^MoFm{#zHLUtfH?+30;WjjLJMxJdBpy} z(Xdytr>AbqKI(kM34{_MQ$rGe*j&x5`JBOpO{X4}Pci;K#o&IzmoY8u+dGyli@*H^ z$K7uEH2Fvb)F(_rp!*RNd-)vrrJjJKlggHw)pY|wP?q)<$Kb~J;LXuxt?l^`TChHK zKp$_BUE%MS536w;?uMgMyhYZYlKb9RW^cQejdyNp=ZSi?PZ#VM&@NeUycq?wfXxSa zFBSdb#NHO`ffe5G1-y4ZV>tVs2u};Zr*o6&vo7T_b)y-sxv0Ls-L~O?CX2a2PwVYbNX$E&S81b>&{HP zUYz{4^|er%EP2zHrO_g(>KuIj{E{szv=%n>S@Mh|Ndc|JGYGEobxKe8QO7rnq3Zk zH~?2z-T=-!y1j6~t6-axIIqje!}+9rE)HoGcBGUI#HqCJR3AMg^U8tfYU!grCogKo z#VmLaDn|Q3A5xj|Zj{+@gn=3{eDqn`(xfzdFzpaa5{2na0l8 z!nKaMkY`m2#1bWiC=2nITGY~wME0gU7>;bH$Es4%5QIaQvIOpQ6_1T8v zGC%4XXGO``uX8?UBaas2Yg%hcanAQY+nhOwYq%H0IS=7pQ=#ueT+h8M&Yg&FWe~QI z3*^x+`d#4uQysYOO&bgRu1q~&*%-LIV%DK@2lei}6kUf6I9Ja%~Xm0aw zs1Lv|+S?--=bUGZ6F%v<%=yINF9y%SGr0b2ax-2M6F08nJTvq<&STws+2yf1|1lJu zTi<#|=6%!pX#Stu%lAF*wi+LSvU9(Uytb_8LKH-wLe5cYXNM#=U3NUoW8xeC>=3i6 zx!F8m$h!aLv)w13&Gt}Fy|cbJ#MmeNqv9LOqb4`?dq%^>0YmTe>bHkX3cZZRu23x( zUvaR1&f(_!kA057gP_0P^3phbBM{0noFP9){j%52ko&5(2<7DyCM8|3;+MI)b5-pQ zLF)3yJ1qo)GK(y$9uKHmTDvH3Q8W^iY`bqYsN<{}(|EAGVTV=vOTGuXmxYOHzZAr3 z#HnxuqQHN|zLJv*!47aI1~15>Sm<~`VE<;cQIm#`_Jd!O{$&dV8brTMUCmlesS*hc zl~SQxTL<*a)EFy)RE+J}G}kR#Pj59A=!wM=qG!E2ktsJ7lqn+3FpuSt{FXdrd70c( zt3eS?35Ik+6puugCC&2N%5HPP*@cZp&GJOlxLS}P9ijyNsCj9zqQ$yvX~2pD>2LX{ z1o668L<3AJ8X+Zy~^VDQ%&$Hn&7uwFG)1 zk%-8}SJPs-NUoKxMSznNX`P(B=S`Mbmm9k<$TqD$P~i-(2zOFO374E zWbT9K41%~%m-{$5mzvxs$3b4!IW!)Dr1l!>b%3Iu-;u|C5b_N z?M8*>_N%XwS0$D!#R~;GtgC);tk6*l`}MUDoRUUqH_dhcDF_aD2Db1q0Gw?A^goCDgN*DXG{=I@&vSmpro zP_7dOqO@r>_~_ zd=b}hE5bRq9+%@Qx^d3!(Odw`31>SDia7`3Gu4wgA7NZb$@us}-VVOXwM0^_?%AZ- z-l3BnAhnNG9dCkLm!L9XWPx*rT~$8`LnSI3{B2J^ut-uwz`vQ=gjJ{y&X z#4Vnj%+ZbB4+*d}`gmo)@sDr|apV!=7}OmrT5#<)V? zQoE(LZf`p_@$>n1K+TDYqI|wBHR(qY3V-IP+TP7wbfn@jeOOVi7*LB)1i^rAv`MQ&7=Y&lN_=~zLMW7);$aGqBwII%kS(Q+s={ghRO zCE^*Pz~x6p**ND+EQd4lad|F4z=`%NZEEl}98f46Y{ccs6RkMUZo7ta&PbC3 z7JRh6@$L{l>zgow^YV`SIOl-K+3H8Q%z={3vxhK1xqaUm=bW#!r{joxj~bj0we-jY z+_t|d8Rt*OAm;*GoN_u}v@%mofs0Jd0l1m|RXLr==G!MQ$n!rDk8@tkt|>(Xj-=uI zs}2np;A=P#*>(NGG@!I;tSZOl@-wwK=kVPhV!Gd6i8HvHp>$}X?OQ&m4mFE#(7bZt zzZ%c(iMzV!zP}{>+Y7HA6k<@vj713E1)q#NWP-{a8d3)xu-P*D4pUD`*7j(yY}Z5DLt z`>xXpr;^<#4zGx6Gs`J3=o|Vn%jttV7hX#oa)EnUze{>h1*wHsq3FRums}eNIJL^> z$m@ZPFO)kKL44p}@|zaluJwWbv_BRGjzRy}aky0Z^<-`cl$5QlXxVV1cm1tX31IL0 z+c6xR#VhqB933r$#M?=bHTvRtZ}YSk(e=j~xzVzc&`&&XEp({b=6_GUJ>~15;y1eb zelZ;OY;E!jiG3!y#3UDAzgv%{dw?&AwJ>Ms7@+l;;3F=8>Es6CSZ zNnf6HplJyPJm1>tcIxcAu%y-C@NdR!W-6>Jy|m!yx+A*<`EPSsllX$w<_q2_}UjM`^rGQM3$wmAa$9XnZruyzITS-zTrR z_F}g60 z1mi2(SF{fXoq?QvXOZB{-~9q1X; z3}Ph(qXk-etIa=uSLgLD)Dmqi&6V;c%Qei`=^1HjX?>)lrK$DR-WQi>X=!a+|Nf3s z$QQ+cfk;H{G8PM-dB%M40&UDT3xQg2#>}bxhWaY0lyzX=$z63m0^6Gih&BNMFg3Jdi$8omHGPJ>b0E25VdTGI45VVFM!P zBuIMJ0S=rAV9Kf?#)awHa$GIIB5QeqakIYf`f-bq)9|1gl1zebK;>T!T(#`ix780- zfApcNFz?Mo%M{J@jfL-K>X$C-)3epTq<<$D+Vo0R7wVl}STqg=jxd}HJnRzypqrOwhr zy~ur-I$Vk#DxFdO*LN)&bcQwEq$yIeJANydgN}T^o>C~qk>sUFu7m|rI~=}3q9#!o zAfwYD5X%H@!sD=xLXF54uA=7@rWU&T>n-fzE5q;JKvsG>8k=MrS7}&p(lgW5(X|6z z6CHyv_~rYnb#!$1ZTaT)^Hd3TRct1b!EaX!;pNvhZP3$ESEq{M2r31IaKEvEwoFc_ zp>Ofu|EctoSZ<&3M$5-Uu*2UG7raq%tP2)Q<14B0M%3L6k0!{%qr$;S%zg_L&9D^* z@U};a#93&$%irNt&vyh1WflE{Rzodi{ zf6QRRGFNadIApNc61jp5h@WASjH9Bl(2VrS?}1!8M|#qReNy(KALqN4xFKXGG{qLq^LnM{5PlNEU%9;Czjc8p^c#9Fh<4?4^zmL!3{Pi{SfVL zBAy^;`A-NO&4_EE`Z&umvh0A#@ zzBvCej7GlKXpV~Ikm*zTxwwask<&QmhyLI*BLiG3@LAXKTAcHF#vH;u+;AP&Cwkt% zdH020oIf1}=d&$2^BN8*j~{ZyD0o+lC(gsJ`QZHNOid1b=UjB&4o=%8Sfo=U;6HKf2krFF58O$@xbi zWc)qH8gHnm(Q`>t_Qr~Cj7GxU<`8h7@P@X^NO(N%5B)=bxSjK9uU9&pPKbq@$-i!Q ztAoDy1W5KQg0n||d#>i|%&VIdy7zaDtS^sugeV*rtTpSMyO#%{COH_22h{sB)Qhru z(V=rOkQ=ZsyYPMAi#tv<#lumrE;!Zj`Gi+2Bza-C&o&8A(h&`Qmp}2roRT90uSC`w zUCgqBz&Lm4sLX;Z_4e?J-FegrMe7 zUkJ5(HZ2`BpZc;ibzfuOUeC~dmF1t_s`P-G;n(^)`oSgSyM$6V$W9-E&XMDAH~BYN zXTE7~d)aRAHy0YxAUQY-+{RW;R5n5Mz!8}62nN6FFAZFH{_yzDi?)6c-I5H$xVKXU z=b^A97RK8TLC@(p2r1lK@A64Nhpv4l8o44(O;Xt(mnUCeS{4nX?!Uz3O{bF&T<&_| zZpND_S#LyLriz2KChE=NDhw}IEgYEG>yrJCkt2tw(Xl_}+^~ht{w#FkP8fuxN1%{% zHK-tRM}_}R$1A%k&yPdioyRbK_UG=$w&P7EAwifcI;PcTd~D}E>q|YzS9D9~iY!Nb zS?;LwSi67ydEp~@<8#LtKTR)TzQ{{@(JIR7h{i)z6_Z34zG}ZRT=GcGS@Kx+AP@x@ zdOnkG>W$z0OVtMxBysxrq7h-1n!T>4T7xR*nGQvt?kLL9@;021yXX#TRAPtXi+T?h zi_RZ`Tt(D&2WjS_BuR{XPi4IPh6u%lrd5-X{#`H)mQ?ulZC3{M0b=; zs{<{|G;bK*GwRci*S%qK-XuqnzVq%7gqk;7#p__-glBcTbW8QS_U0@M{>IrAl7!BS z-3%TmQY3@QV6|+GQ|{ohCC=|YNJdQV=oij5yz?rs1M6W=nG$Vf06e7LwUwlr#BtE!O;*K zAR4=yHTa;w<&W>ddEy5kff6rXc?!NbAXCaD3a#aPaL(+%2=*hU}e^U7B?Vq16ptxMAkAjTJ!;CalYyTVlhK+ z^aM2(Eq`r|rS&pPQ>zyY|26_!HZDcWEEZdpf>k8SK;rieWfHMOmVX0arn`q?ldm{>w3 zxX^vV5P7z>yJpLZO4LFpPy;w%YMk(pD0D%g>!_@paMV0wxEhPRmT{!kiCu)%gP!A|))CzuDVf3Y~m8)7!N2nsI7&%g|D)(=(RGr_J zx9?Yurdje_I+v;!U892N(AQ6@yNXW>&&cmBXk zX#F}t*XwoGl@eYcnhW1q|1+UXB$UZDmrx2TMk*4ERvSrFh(54HESAdaL47l3G;Hw# zu?7y9tdxqkN#)v%hFncv0ouJl)qwiXgbPFxnqDW*fW^NSV{Wka+foT5GSp$Tl$t6H zrErTUXz8j6R0?&)FHK{h7{F3pB9@9IQLsX!lv=)G2NE$(QSklNJuB2{3Lz=hiWMTD zu_#C^qJH(kADHKH02rxKsc=OLT3Mtu;h*U0{{y8tH9mBIAd%+~T#j4JA*pFC z&`M5L#qoDJ9xW$!;qyBSGNLgTlP~+`c$S=r1IIh$@dfcT>UtX;)sAU}xV`>VP&Rt3 zVQ)BGh$73-n3K_+-v(t1ZH`h8@4~$h^vLS2s(rcZBXn;74XE z)6@?*W1(KVy1>)CQO8?5O}&e@N~PN+Rzdoy^rSht7{6J-AuX}|Kp<7a5CyPXO%X-N zB;9a`l~zg1n0?u_Gm4cSF>0gBm<~zzLJw6o9j_=5cFXED+L;1Hx9mQVH|Wx02_PX7 zW4}ofHJMNZvA=NZhHQRx6XEO_>HQU8gb({jnJliZHjb~Bn z`g%$V$jSdWN{gehp036*le*%T8lQ^dQ%?U5&BRelb5Tf~!pBUih@*t~Dj;*wJsia| zlh&C_cIzUW~t;ljToN|+DD@b#I- z7iyzXW)5>QTsRS6(j2Uto3`m{w_)FPWt}w8lP;)Ch9n2AJD0=*?zCf$aaz@DW0~5% z;mieB#>o%W+UZsmzi87g9sJRdZx-k!CjbdJH1K#Z)Sra`V9vV^09FSB7KYOF#&FnRIdz`>6{u$TPOp*7Si7xWA znpJhbup9b|P30SL`BY`^>Z=#buBK|=v(Y17kc%8|;%WLjkQ29ov)$4{u$h>;LITQIx)pklX-8R z4b=#1fBTg4;=p^d_%^-dLOTdO>H!aiwiY^V!4To=v5oG!A;t${{%~ha*k^I81D{K! zPs^`d!*-jN(Z-$1L%T2o_q5fi569x6;@vzgr|**wMt!#K{?03l?7z#h7TjEPM!0Tr zb@u{S%2V&sj`XF1yk(BT|>lO~a zR$+pNvcXl>z3rOm0f$8wbzDlp**wJtE*-{lfs$2jtK43Br0?;v{Yj|Iq*~rM`Vx6S zU41UgFt<%%veZauy>t%FW`FV#^lRksu%<%@<|2D%HBI*h<_tER*Vn<=Y=I&vfvZi#4D3 z2Ys?ZeC@rcS+h^Q2(|pINPqmTLx$(9_La-)JfTYFpz6`BT;jZS7%8xW4&9`sg-iNZ zonSKex$Qr8b zI#%JTs+31Cw`48v(!Kudg%wfHn|g_D^gMspw4gxu zq{V5oG%H^XAN?`3`S?nYPq6y7{PK=O)n+H*A;U`fxiyz{ijaejEf6nTpuSAkNVbg@ z|1Q*Zoe8}w9-5G1rK|AAs+)d7=i|y_iRuLt5cBZkJa1WnjbTi_!ey zuC5C^if;I$95=ecma?yu#nw`B(acRpS;%AQNq?%L5jDCSXyE`S^UDjQnHm&hTXyF$Os)BmdbpM5Br%wr{B%D~wB}(y4y%P-XZZ zPUft0911(gVQ@e>EK$Mn)M@e)+u)H9a{_Xll4TIXrGJ%W0H;}udxEP({Rdd>YL=m% zNCwZDhT;NeVt@-IQEbjhHmv8=-s0KdH-1s}iRR4(`!3_tYTOd^9*u;t?=9Kpe9a`6=x_{s`DBt)#Ut|^*F)rXg#FJ_8k z(6I#lylyI_Qg~M1qs#P+mpC;=ukfO}U7q}Ow^nPI)}3af^EI?vklv+n+TK^vw=Mdy zev{#$Jk+NO7z~{ivipDc|L7*%$OqmbfjDPBcw#(~N6V+TIoS zRnc+DOb}C1D9pdA;jQg|NYQ_l#$sB*cs1uh19Za`>a{W?6dUKmA&5agvv^VA54$(K)Q>qT ztOk^Icp2DOW|{l$4u=k77fEg{gzBG;qB{y8bCkMK2f;SbV0bnfy0fW~?O8{tju-s) z9?Yw!^Q9q_i|`hfY{x*N3o2<*w|_O(i)vu}^rKDsbD%E`Je-9cuX`^YHFR3)cORT~ z4gaCa9x^9jtQ(zcCf}2F85kQ`&>K|+px#R7LKzMSau^H~p`Xsi5A*GADu<$^SPmS8 zYEcOD;YBR{-=BdxAeyp=Evr;@@Y0dF8||vN;jOub+rk5 z3BL_?$WF`>a6OD3A}4q-fn3azmn7rviiyB~y(={(6q?fGYAe1>dZW&tKmmRtyXyp^ zQEhRzVU2piN36d`lDL2-9kGn>sNxA9EtxTDTS;IvQBfqtU3 zhY%4aq9T;d9g;3S;}0L5RT(2^yWb>veG0h@$Y{WEHOWm$hW|L`$1KyQ zm@+=#_Fg`e_lh@;?P76n_=k1oEM zF0KyWaqGRK8f!DXDD+=>jH|IXO6 zvuCV#CNgl6_-)XXpPp&8XjJh<7pp(eY;u(BddPw(2B4e>h#|slw2I&cWKe=9U6Lim z?~`+_k4pG@8%7FKeico1ss|xR^B~CzC zF=c;?&NhmR)F_Rj!*5ch0h0Qg(7VY$Qm^?BVb28&QHQ_64{vB=R$x`|o2C$Ibdaw9 zZCO868Z14@j1SU2v2Z0-%gJ7p_7H~esyV%wRKy(2Ns^|ANRrw>P*A+cEe{n%Q)7qe zyW2@mT55VG4}p#Gh(7h>Rj|XwpNU%Yi8=nN;gDyJrsp3Gj(z{I71r+mas|_`&ap4@e*)gNRij(71li|JncdR`M;Y1#F_O+-B z)MIh@5&FRgr@sjO%*y8x5)-2oOFrwA+CLyY{NB>t-6>JTGfg)iOzjf}^`m zTKZ~NuE}B!TefIB$cCjJ%TEZ&_F^POjQSERjstqNSdCH&tn{P{C|ztlrN@wYHS&|* zKsiENt?@Mn?=oZ9svt+`vhk+GQLU7UQ*FMv?1Emc?AAgL>XKf8+Ew9I;X|Vw;Uxlp zjO3I^cQX>HQ=)A$C8G@+jTM%(vAS4n!YZSa6Mj>K+j&qV{5Ht-?gpB(yY=pHjJ(fcM-V_X`d=p1+7h(Fp6wg zQWh;NKZ?={?V;%Ak}glP?lH}f2k3URdViuS#UDI!E#v!S&BwKat7%W2bgkYf=CIrH z%2KA=Pdsq-^;0#=PI{T1DX}chS{{)snT!%$@_lXS8sl}7?&+p(xJ$ibAuiD zR4L`iu||Hn6rCHO3RKP8$29Yo){atvD+=y1r5&n@VmRJ~yW{?yI7f}7i|sVe85|K6 zs4kYG8pF&NhgDaqDYp_7pei^pnJ+E-{keMx-*K=M6{ym&LgkRL2a~R9R11fm%h26N zvnrN5>2ugOKJ&~9@v4`V*T7I+pi~NlDrSQ}TCp2@c+lLb_0Cze#10!o35EM3K_tPB z4MiHFZ7Ugz>!4}9Vx>?IX<*YN+T=?DYj$SS^;r<4NKpg`JB&LQlq*ITdYTUyoyY>m zb-BuecZ^~d1Sx_PmyCiGu4a>MIB5FNiXcU|nXgu@X|Y+jWy&JkO7L7h@ob=i`*&u4 z!dKbKvpT~}pyH-+lp;zaS-pMDb=?fbfN9q`7}s((t~ahT2~?CV8Ago;wQ4yEd&z-L zGxq~`3E12ztiooCB1BjWbm@Iqv_@*CW4KiRIUBV6=a_RPeb*6W z8no10s-d>UT)MOt3@ywTOO1sZB8eC+|22V1JMrItB5@?6^-Pj+-47rXXZ*y;8)r%y zpUy{}iKOv`+A}v@K2P=O8kQXFn6C~cCpqMFANl-P&V>BwTv<+fHj{6~)0x9>Wu3$2 z-*h<{)cMl+R8;}hR6;w1Y1mb~$MI&1a~EUYY|m-2bAxqhVj$Fa9L%X9qYH9__O|7HIPCvX<=}hKRl+RyZ?VAZ zq4T@xdXZ`L*eNU5OiRqfEoqZsCT>jnvf6yb@fgH0as998xQ#1MHW1>4Z1VyEjyfB) zRUqhHC@6b=#2&>*qUJDg_~OJd=uCs|7eluASfAYbAQ3JFL;Ej1O`KIlhNU0m9EOH1 zr9aPnzufGqVT4J^VmEQ`TOD5saHOZh-;bE%ZhN-b{GpFvg4|u?LM$MrBKVEg3&usqBq62fO0dSZ#rws7_wW9P) z=J!lh0Zt*BZv{D*s=)u)Kk`{_98JSHNB!Sv)i@tj1fRykI)Skkj#t3BPMO~;zi~Pm z9@Z_oQKa_BTQnH#h9df_(5 z;{}QnnXAWh_9uI9Oq>+E1Lq!F7*61dqp|pxSB_@8StwDawh9REYC4!*$a3JjwkCMaX0y47p!`ti$2p zdH=O*%@7qSbW9Or-Qp~)>OFjX-HnX5oI>9?QKnYjrq*h&ndYH0apsfE#7~FgZK-3U zJBVU&O>sUd9XCk$z)P-8%$f$Nqkm>^DBw7mj9t+wNZD z%B9E8!9(miG9nJOgwyHSsCXC}vQhf?{!79bfmL5Dq^DAc^!?YQebu>r>`wy0J(X)D zRnf>~9-}gkQ$dNedkE$5w~@1Gun>aGZn1uuvPL{c%~Vees}%>9P*?dpM#Ru zE*&`h)dehE_lB!)ojs3Ano(gRlNgH}Jm7}n(}qLAs?)TKmGQLHA+eVJH0@vwZhmM3tnyE0Yrftl)?Awzp~Z{ zg2N~uoZw3-JpnH(wH04Fpg{Pu9a!tib(PQP8olzOK(3Yp`_I8X9jUstH#&)2u^^}v z_F93CN~QUWrJsev9#cyz{9-ASzxlv%^v!A^G6jTOAP^#9mzG!{`_r1;I|LFHz3S&S zXxsiCI9LLeU|?XF4tQ0tjC-GWSh1}Y(ID=GUv&jZP>h|D)62KK(-g?4&e_CUbf!|g! z4yf`snK%hK!_IfMdw~x+27h#cXBMg%sx}&|-?I&?nDjw5^kT8*zUN-}Q!=VTryv>b z!|&G7`(A$iop0CtDGo0gD+@Uwsfit4O{bAPSW5*aLW=f;AV`OOy4ch2A$Z~acccd9 z2AjSvM<0HXhLYeA@7dVKj&<4ro5l0(}JI2VRec$W^@hbv85;whCSeV zl-}iPgdMb0eLX&@9hP(i9t|*Q0UHMwMWqVcZL}`sn_zeq;-qsc3yl`&kB_OMi|H_D zG*l(Kpgj?euxl0vq9?bo=gr3{FJxe2lU5;^B`C_77dPP*j}iZ0x6 z1MB54(5nXZ1BaM9JA8Q#pY-QM z$$ZkEzo=)^ls6f)g^RA}xQ2@@Ws@d6y&@+Wf}$BueIEyicz;mi)bd;X^72h5ZZYMW zQR677j|o?FW>GDB(qp0Ou`tS6IHoA9q&yV$k08|xCX^4N$l@{C=c^trY@%H)Q&m3N z&Dt*NS?c@J=w_?{(5`MHbXHwha9@%}yQteptP0H=u|{4RovmF*TkRu_kw}Zy5Q$|p zCPXP5by=uM!is|?mS$5VHW<0aTYpS)G=-@(D}T#V%~7{;=@=@aLiF=hx5OD(&;I^~ z9<}i+RK_}6NjB}WJXXVY{r!b$;)~1DRZ)sC)u^h52-0# z6$Wp~pCQZ{!e1eYWBYOCA8aOxbDDeqcs|Ad7=6z)Z{Ykl{g2T%hdBKI(+?CEn*9H) zH>mmWO%nZVMTc|;OWL9QVhu{jY(XQbDQKuZ6D8#zMj^r8)Of=H@{bQdkNvJfLC+1y zxK@O&_>Q5JGGA2ZnF`KW1Nm`D5R4}n#ofJzQU!Y*`ZWSVOKd!!Zp7PREf>vqOY5@7udkD z>m_jbp84f5Nn7Y~6zKmumntum=a&Zcz5CNLzUj(%x#yx6IQI^s8hc+V_;@6o8LWqX z-v~6C=?NooyF+cIwvlh;UEWhs`$|;nx6RkTjdt;edv&g;BBl!6uDgXM(}y9eE*{0i z)T6}4%Wyc>72S>qfXbmjC^%XSj@|vpBcTd1Pu@iB*X$|RHb0cmGXmwVonSjq2vMcY zke}g>S|=vpSY`?uu*pVlUjFDtkuNHCNrJNbzZ*-FI3E+0I;vO1zISD4Syh($vD9}S zodNfAPJnqjZUe{Q{)3IxoxgCae zumO4vy65YT%17FfYiKK~&O8jhkKG_DCK(d@Q&3v>4XmDf0{Yt6EjZCN5t^39Jfs>b z(eMkUVN_WFiXDcoPi~i?JG)$Vp7esyBw>sbHZBq6K6|f;x)Mr{FN!RL_K&MxJHPWz zF}fZ^H(#M1eG{nF5{RM?Q|Hg3q{DFP)w-7)0^q?g(>5lI9u{_b2wS^_6(^aIBb4_M zG?9hUcMa?+2!QM`syj~D>p)!_q_V;&|5_-x3=b}Y`w?ON=Bsawd^7A#*FT~LyXb)^ z%5#Js`PH2!;qafauY!Ty4ttJlyPE`)RjA??oR6h(;c_VQy91f~>VJ8Ex7R&X{fmMP z9(u#*%+eb>vws=%rt`AmZ4vu|_J_Xc``NK?{MH1%*tE9AGD|;RoV~SYYryW7tr2P` zOz&^^^%puELykpoI0roce&e?>&z;=tvZd@0y7`bAZ>Me~p{8<3NI`kM&=8OA-3PZ_ z7uF;^Q&NwD?!kC6eL0hI3ZtrTQ=O(+I~tiD$sxH_dotuA$K$9RTx`L8Q?9}JPXk{F z-+Slfz?b{C}-? zusQZ)e2EhT!?Vx6Rta7x8OM|R+dGT@cm#I7a-bTn!FdRSd+_2BwCy)rwyb{jn?JVW z%m49TuRQ;pH>@*|AkB@N*DqRZy=>Wwd#pFETBs>mymYChhW?5ic>ARtFIp~MV)_1I ztf3SP+dtj^#^xmpEPnYHAN(s0ey=T;DAi#v{R-N%QpaM|GaJ`ghQSr^hL`-I5vp)> zw*9;JjbP^1|7z|_0IImo{rS$!+ttC@!doY$5_8?tp-Rih{`Eit@fQ!?k09JZ_r4e-GTbbI+V@ z&V1+0Ip6a8K6s7Z;3n6uNJx0;@bc6E@LflOe_XeC-7ZLohI`)d@b8r-DC&j1k)%8W zHvW1|H9RaQ0g9SFC|yI^1EI(hZs$YRK@zc(oc*>+e)Q=Jo@D(2l2;E^LEwd$cFEAy zOTzXsRd<*ho0;oPkbaFx+)1K05#-3++5{or)V_K;j})(GZe1d`j*`4Q=;?;q@fR9N z&>FIR;+9HiYlg11u;D7{e*o*YLvAW;KLp_s0{$5?u>8Kyh(37tlmr3 z>>`MKe(x@%BPhU%JF6nXndm(b@=3nDgVgF#IJh?#>dr&zH`}g4>qP{mAjPE+`uBQY zSo=kG00eyQ_hGEQnM*2%i?<;#7jEu`cv$f|&Qe#F!aH~w(*90Vql7DH$vw5LMp z=b?X3&V*Y9aC9R%_vQL7=*@+z9)+QhR{{}%(6tFpG(r8cn}{h0B-ag)4llj%<_T0?FM)C->Oaj1fMj^# z<3F825Q1Y!vz_opIa%?y*IxVao9p4P0c0~0T_C)*^us2^JnKnIu4xowqWP9VZ(m2;v=-ACV!;6fpjY?H)|^$>atRG zR-M$r%1+p$TiH|PC>d|5^OLt{s-BtPIF9NuOMfzcqS<_Bu5p4vE2HvIjOUGd5Qv-q zHG%^>mRGN)DP>ySsDVwFIHORMl;vHsX&vZh%SiVv<{;iAf&f~Im!R95ZfwBBAL-3N z4c)8pygWJ;4eM9_$xWAF4I@(S#H`%Gwn=3h;Z71s+9^v4rY+(k@sV3q%bRKcAwq$b zf%Xo{6_&42t>VvsP?_+PyDb<($pX_17mRMAE8^SMnFL0k!0!;)cVfn!;L1XfUXFxi zCuY-`q0n?*st9RB7aOyKje0cw;2v_}i2Uka#cl-7I4|vZXr0-vN;}H+ZgZ%(X8UL@ zRY2CAkSvc{<+)38y+Lwi(dFGF_&Kjv&Ywg$%7ZmfYuEDZDqlbPBy=2c-U$amx5Q!8 z0(OSdz)-C}-3i96c3PSr9Bqfwqqe$f6E)noJ);<)+=`{p=l)GvKpyfyU^M0 zS;kePC9X6UKTm}I{{9D|2yxFLtQvI$fmb1`j$VLF$~FP2hziZr5cD6=Y`n7H5N@^+ z5|09N)3nHGpkMuT&;TU`;N6!1)v(}+j+TMpr+>kNxE;y?$-iKPI9P94c&1{_Q9B$>B(!z^5AhuWuL zUdm?a!Cc!z+i5entxP)XU>Dz}C2>-g1%BQMk?!I?vFfI?FNew|{QUixqw^xhVz9`0 zIJIn}!Gs@iqiNhQ!T1SY80r~;iML^*SL1t7xyA}Q`4JD(=y?Z9W36BTJ>Z9TC`ZRG zo$tjJlL0GTo;i!xNMX9u@%QM-u9@wW0EwPHB2r+WBbR9TAH3Bj)hIX52I3OUcE2N0 zo*8U~DF*QtOH&4Gt%+Tm2yDy2ZcD^0XG#^ZZticP;|p?b@{0+c+C%8_P;mc>C;SL7cYv7GL?d8d7M0w9 zX(2uG?~;7}$iFKvj&HzFqsUzD5Z}fRF>=09od2(U9%SLCEKtlmjuEoYldt~TZe(&@}8gNa69 zyE7z!$+a$56{J${`7O9KWVQPQJ&FcMR`3^;*vP)ZFe`(B3 zs;TNEA1#qcQPjpLSx%~xE0r8#8jMqLsQIb0wWU*sQ$Kjh3Zw60<$s6JIuJSA2ipqg`7cNje-Hn`NgoS=l^B*i-9a+QizNu&~k zlAfq#&MFxwq&A~RQyQYv$#(~yq|8ag_%4SVqs*qS6PyFc4Z}IeGp^6 z#Em{4`Y{LoCd@y?H(?miq8Gbe69$`Dj4j-jFti5WoEY9s@Twzre<2JfL3I7(5Hf_U zw@%&z-MbcSi)F5yb?!-?`)J4H%i9g-YG>U)OZx8^f}8DP6FhROv@zSpB^FIvZQGOk z5pEtbbLnErV6r|px{3=Q|KJ9>N*WE3(nzVv`YyLqcT4x-rpwUa4|{AA+etzfDWWr& zL2hfUd`fOJYLwgtozuA5aSP_tUNiWptEZeJvFhq^%ey4|{m`9gld2DmSog`Ei?^v( zo>Uh&?RPGwqspMv%al`z+8z;oI2&-(OJ~lZ&x6v^5J6C_9%2Gkn*>8YVZ|o4Arg$k zgdP>pzJwkXuf~Lpm$2nBTa>Febx5`)+Mf4y+1#poa9dXM{>~Kr`Xlm;LQS4F(P3S@ z`Tiiq{RmD(E(ucBeQPDHqZl#AFvagyX1x zpZxZ_4bH?imxJFqKdiR77@~WymqCGJ^4$h!f(@aVWCoj^32(xY8=w79P~76T>ksRZ z=l{|dk9epJ29o*Lz3_-Z@C3c|;h^vYKRYAwup?4j6WsL;_|~yRni_dh*X(J6yY;`S zJjLCtb~>B0fmB7;K}gtF7rKmT4RfO|Puh6Ty)cEzUe9FqJ9X5shkbJq2V2_ErFevb zry6bXK27L}XCBtL-+nkA7ev?kXPu7K`uFLMUU>>Za3TYB$B*bQZhh8N<{0VCwkGMf zu2#2ovfg_?K*i3JZKJ~Kt=n3eBTY_Q+FdXFBC<{rUCZ?yl(h%TBk#&@RC3t|6t{0` zW3Eko_)w3QhB-Ik&^pLzaJ|!Sy?Kv1uYTdFG-ZF|(xx!xK!_%|V}_~Key^94f8DYx z#hQvt`L5*IDb;Wr8n_4)+un*LEuJ%*j3`j)miTZ*P-w77(* z_f|Ta>YdwKbh(@DF5I%)e%ZdQ5!#lNrjQtvf*zM`3sw3YRCr#RU68DAY0%ak;bJ%H zc2y|Cg5Qie|IEJi*1Zun2@%utZmDVxsB-db`kTi_gpa+LYriJfCaPwXSDqpNlJo6k z#mP*j+fcg!yegQ7y$rf=yS>@or-R$uAxjQpGKw`tVUYL8`HcVT2Z~Hg(se_L|0dE~ zJLXo%iaV*raM|BxYp2c5K%3ybjt9H^z^g=8KsOqZ#b@BA@!}OIShg7x8M# z{1EquR~y|M-7kGywGkR!FL~5lM*Pw~S)F^GyWfKSFBB~6Er#8v*^;*kmic)2c+|Q3 zd(^+(=)Qk5IrDU#yUE($BfJ#)^ewK9uE$?J{8gXquAZi9bnnxbH$#JalC`IO%=11T z@w2g#JZPC!u#EnNxF*Y+PBSH8Y)}w{EiCkSpea~Z)eH#-Ax{>{Tv*UA>(f{G5Fd|5 z_v%^IHWj*Sqk?xaD?6tjD1$y(vUS0-ji0tH@0aE0L$bWlotC*S3h=+d{o<>2?sxSy z!LX~0-SKIo`!!#@v%?$Mf@O_(S2e%6tIwFywye#yHk0+U51A9!Mq8*J}%w;hii$iB4+1X?i#DYucStemYm<^7>eXfqaKH#8SBK<(4R2pOot?XU7 z0?kV0aw|D2QAs{E&il~XPNGp-fBKApGKV9t&nJ;~G9X?t_jX?29Q!_a2a zW>YeCuUi{;%|7D4T9n7+O*Tz7kH#kHy=?b7hyC{jCokIo1C495sjv+vqN zW}AJuOP6)M=7=KAcC|4<9*!w!nlkII+4OkQ##`BBla1x4xrH0NZ0X-4E+HAtdwEREQ%#QK# zl&=^$f5j3TkM~{ewOWHtH))D|-mj6LteawInE7`{gI=f8+3H`m(P{LvaGhi8BGF)s zYOzkG)#v+!%0pb~6^gG7daZ*-XHc%vC}mQaN)2j>Tr2-#=Ig7*j8__TWBlP2N4mO-#Ls_d z;Ma9SZNTIM+X=x=P%yg{G-ri3sUXV{_S3>U%e;V<9~ytny+!fB2ac0K6BC@G*|DCf zq6=%e&yvk)#P2L~r+}mmwp1xrBXW4(eyb0Xy z$4Q#)4~LKg<7vEZ2_kv|#IjDSXvaRWF{02=<{)07ikL_}sE~>K%~JlzGu>|d;}owL zX&E$z25}ie%#cUHAtY`MXnJuT3mihyO%Ti&%%dK%LLpLMBVisIo~(8xo|Nz=`GybW z%g27;(Tm+s4)*^lX5s+^5Y6VhhV9H>1^wL247eE^~~PYnfK( zw6We1R61bq4c;iEc1y*@{E5o(9J1Kpp}zp!tp`_r&*GMw5mu6^Jy!_LcL(FQ8uw&ys`3O^jD z)pA-Tf5yp`ygz$N`NISwl^YF3n7RQ0pYW^(V{Z#vB7wO!n5`yW<_qeQgIH!*udejf z##YI$XyWq#?w@DEDi!^Ostw+n*r$8$j0@=__l`mFZtaP6OzUN4`*pfO*X^;r{mvfr z(N|6)$?{A)w=;I(u5QUM-EfI?-A*^7I!;6J_K6HZ)L|mNKWSW?qFK8|(Z_fyLLo=K zM(K7+5oKp}`;``5<=73BmxegHGO3)=D?qNbnyyjvxiJ2p0^eEYIPuWXKOqej8?gx6 zcQFrAz;6h3YgQIgep^x^75nX~#%Sng|`0LHWWQc9+U)8oM{9+eE-Xaa={q31a(83*iW zq)I8D>n?OM-r$y{Txr#bb|iPnR^v*mmH9lm=s76^3Sxz*H|%JFNOQDbDEr`_gTP`Q zh!uC*v?rKjVnYh?OF_K#7G?&)sA4$hvtpi<7RwO6hNZ8HK+DGqp~z7m+o7FEK%7*(+Q zV*`!VW6?yT+6ZY7;-57U{waTUZ(P`)} z!$6c{q@a?TfZ_}=62{gANygcRQKOBA)}B$bAR~GX(CNgk-~ z6{0TV?oT?cukb`Q8MDPlisGU}>57`a?w?Y|9WY#PVp=}%L^lh5+#a^bJ$7#H0u!Xq zJ0|y;l|HJ|VgLLjO#@f!R6DLv7Vt5xLMoF#Z>4pWfmZdVgj#|ilGBtiz$`N`=&D*_ zGnN=|J3K|JWm3ke@f3h8q;2XS*nMeHk~+lpZkR_PP8lu~%5fgzS_#)0YeUsoC!&WcU! zWN;*^1Hy9cUr`_s0G375EWt~Q`DnWNWvo4ox zh*lRzk4w8C3)s-1r@Kty3NCN|6ap}-LZq;Qc;>z{xUou2?oyt{{uH8DqyIr2z}YaURf#Ng-i zw=0YLY$#k}W2B9Mmza1+wzHn?mz@h4{ouW@Z(=`fFhOyl)a%=T5FL-3==>NrarsVZ zieXMRcmM4;m7A~iDvF#K4-`5vbk=QzPHaKw#4Y2y6mD)A({Jp%~ZG0f75E;Cr8!5Yci z9oLn(ET9kkyMv&Eg_9p0CSuxf8DeeO<{68AQ^o27gFOd{*Op>~U16#duO|i3jG$sL zoWSL~r#i8zuJEcIetL7cZI`dQvRzgbH8JnD@@|-6XT^_q`jV=r4rFl6r>*mMPZZRn z{BzwJ;|zzlF}Wtk#E9|Lk@k53ilChzR(9GohZ`PGY6Tf4@D6aPyX213S-Kyc&W_Lt z!KF*-rw^SPv@~r(kFhiWs-~PBALFue&od{DmA2>f7nfu%-Tz$L&y%J`Ng{rG{L6?J ztI@TjC+tgt>SPFAc=`2Yhkg2EHoW4H3z2B-R7#^-t|kVhM5ed39jzqJ&bq0tZpa#w z;RHi#Twbm0P@i>9RF$dnrj)5Vbj9jIQec~=PLw`UhHDQ^XdbtBOqr?y;-_ymwy94~ z_nN$UO5N*SstUWa(mFOC(&+T&LS!0Sg>t-7p=PX=oJ?m&D;w{mo$BN~S7nE@8J$jC z1f43hB1ULzBr#x6%?dHEaq7d_j@P)tsm>KsnuD08;+{a0`}c_=gEnS9rc+D(2mkv( zu*71?{{%p+;XG)Gm3zc{yTRUq#pnyO+B2s?;M@~R9NS;rj$d(tD%F` zWnO~<&r&#WpqLy=efL0xEGo#KRNR=f`o?d9Yk!iK`nNjI-}^TfkjA6(@KVjA=4QBf zu?=Dq!=bJ4#kRa}4ppoL@3_rSvR!5PH?j|^qmqc3oeaZSeapY9+2Dq_*8#Kjp zy(aC2vfr)l%OI8I1>}BjAKc8YA&uQvAo28gMA+KBF^IVqnnFA?6G+72J5YT59$dWI z2F3SENI~uyv=2H;j(MJg(v$;mvBn3AW4`r_D2BVo7Dt`%$h@)mR>eig4ft)wx@9Np zG9Vk>=cdNKR=DSxM{8!(AB}xEiuAF^2@^gpfX}%)I_5d(&={#uKjn$<#AWM<{o;mjZTu5 zm2j7<$&L%;o_#rlL~&@5d1PAA>=wrzeq>*;r1~NAa0{u7V>&{h|Kt6?-u`7isdbO| zWhGvzy=w2%$&2GfjCyNhG_CqxOn*Dl5VZJ1^(naS5=mm1tK3E} z=JCld_mK zDbTZ(v|WcPtETr zN8lP%!dHL!J+N+9;4rxlX=spUV2_ey`1sQg<1q>gK^vW?pPDvDX0v!T7^nU&7?d*E zl2IB=odBg|8~p0Ix4wFW{QU~JfygyJuz>mboA3YP%OB+u3Yo zi_lE(&mZ-&MsIxy@x>b9lefnq(_fhYKl$>PvtEB^=__6k&ip%Y>zqzA$q6uPN#arBDN_ z4Mn8hDLV~fc9A;^A2>y1!L=aR8UUGGuPT4i^*|=~1o!A6LToXIV%Q7*OzTzVZZqsc z4+<;WUJoo|+BR{q(QJ;1*?okRP3lq~50l*UX43j#YcNUghV}&J3Q|cjlNmv_Rg;82 zWGzpeAM(~A=??1+pErEFWyzKTrm6?hZ^Id74=TyU%?g@SZRd9UF#DiE9vsi18eV2E zY%PPZW>}q1nu;Y|HzZeM*)4~eUE3wwOUMIXl--~2g3`+nA4sZA*T9j5U3Sf%+}GxCIoz5i5?Ie9 zlrp^+;M|`A$1l1piG&d6YfYp#3H*XdW;5i@2z>kO*R`KjZ3VxLB*#zMxs9CPPwsD% z>^Q}AM{tR$+}d2?+YbrZB)N&(Un@;bXQHSDNF4E)>FjK?aw;R29)oal2G$~`+ftbc zIF&^6K^oXwfBDX{O7(;U$S3z;KN?^*pwj4HKDQz>?;wqtND>mBR&j^aBgz+gZimC@ z5a~GlJsqB%KXsNwxA?0GOTI9Hf@2qsQb86{UK2To2AF^LLQ~>cCXnfeez?h`z*0#t z!jQ$F9p+kCv>&PaH^2Dl3L92w{q!X$0ADo1d29ZdXHZL^3fl4ScW*eL$1rUUyx|9b zp7zT7KYcR>t}~(7!d%8gT1hXCpExLjo5Y)az%#1itWX>PjEY}De?8T8l#=1M?*?ZU z@p9+}eq0L7oA%r|^(eX&pYz_g?xQ)FPS4dru+Zovm&m17j7G&++0u-Nqh@8`#!@y0 zYy48Kbx;XR4wcH$s8gcI3NJ{Fj&IDCp*vkXw|{Apjz{eWT-ehHD~ZBcrg2h@p#mSb zXfWVXUeI10f`3QT4-5pbs z3bKeSsP(Z-_Mi8;=IVy{ew>G18{ypv99Ik5W~3Eh9!ejU)?-PY7im2MVWjnZgz0HN z(=gIJu9%j(;<4oU(6(7x4{e({Skg8NDOT9e4&jH{T=x*)k2&wOT_bJV3{gQN&EtxB zEbTYSL8rllpApvcy^K6&CCGY~KT-tm;`pyNyEAGTd4j1U)rKZgs$ zDohNQ0po{tq4HgMKE#WbcH?lep#QBN^YxMTfw>+_UPl9#4XMvLgACZNf*=y&BP>I?@^XTBHdGFo%Ki_(=d~#E14VmP3&Tpf4-3~UIsEi?+Cm+ zO6JxHp9X{5oGh{!8zKVy@mFAw|%$FhV zK0adTgP^EmVIKa`eh*Tm&WSB0yP+H!4}B|q?64@6-<9VByl81(^L=39ka*{N)&rIe zqhY-nepes7CAv6L+Iiu7+z-t|gC&~)a~?~kMspro*GTV&x9I$Sn}9)v`Ly0)!oVD2 zc^^<3wWJT|{aCtj7}#)GbU)OZ&(iTToCP45_-UM$*f5hG&xg>WftnFQgEKu&qzC-) zuX9W5oiLCYzeY?K{43MKrH0%0t!Jj?O8~5D5LOSpzhT0#mdnGWr_n~->s$PcdHNeD zbfjaD)?*GM&ErZl=$!d%mgE5o^Uy`8If(0#j(mB7*`<#cCUuMG!h}Xu%Du-OSKZOp ztMnMwtK$?h9*pN00Fsa;v!po>ZQn@S##1T2DQNnU=J}Mi zjaGtTqY=^_>lO>!M*GbiM%ZtB+br#CS6ayo zVg3=8mw9PQjh^LhQ0aDG?hyVZp literal 0 HcmV?d00001 diff --git a/tests/unit/res/raw/netstats_v1 b/tests/unit/res/raw/netstats_v1 new file mode 100644 index 0000000000000000000000000000000000000000..e80860a6b9599fd1588a36b77b95a8e90a003efc GIT binary patch literal 18742 zcmeI)2Xq_fwIE>Le`e6wo7g0(T9#<_W=rmt{cO2Mj$9JQj$0DjaqPsgojAoH0g@oW z-bt{NVDBW@I|26I3HIK5Z{S`ENsjh7elNRk&)IW!1D*LWoEZRv!OTDZ+g?#yd*e0(PS&7$6#ll)-+s>YN1 ze}{Hbb_V++{&E%Nd3b*;<_d5BN$901-2Xt5W6s@w!a=QiOhbe7&(~!CH9s)x-Zg=L ztONW7U$OsH|Hd=>WBY&Ko%Z+dzCZs5zTo~MKQQawPvr1tLe}xSSC5y}o#ac>s?pk^ z_4?GE5aaeQfC+TC)~ z^7UD5uPCy7W`7!+t#1Tc=S}v<^O<~R*}%@o21d2z2k&lFoUdm{bT8bUkIVf1vA^p3 zU-Mu53cz>VY^mM<;b2dH-(GrO&OSD*e~jYD-FN)QM%4GVREWP&U48N0U+C!8MluBTcEe~-H$?yK*#@6C@o#H<=HuYe;{#bLDTpG) z5HwN+qwNJC^HV?s&Ktw!0vDCwU(pS*y%msGLaI!A@%{acQd+TxBtVj1^oZCUL<^c|V9*|LgEGAg9Km}O~yopBw*NiLM-i@vQ zBEXTen?D8qQ>^H?0eixadjrR#I<`mrp<+=9Ix$>|DvIQ2xTGB2TdP37f6S|obM+A} zPwBvA!koHL)wmMvF6%+FL;6tf?S6EreGqL(8%7gcMp1{UF?38ZftH0$p`p9e=x*mM zs?3;2v)mR@ubCxuv0??Sk61(1ZHlOS6URq0x6x9MT{L7)h3-`Gv6bnP+Mt=%Le#U@ z7G21;N9(*C(6|Lh)V|gU9f@{9O9XCcaKAgco$HBq`go(VB_GtI-VdFR4M1yc1JT&Q zAk?lP1ReGZLyK3!(R*7u_o8^5G8d4D)~qF=G0iEcZBiOKgE(}qigIncjVS6R2KEG`^*W!5FWF4BOXh2=Vn$Ve@ zX0)oa6^+VhM+I)3=)iOrT2RrA21N9t>#9DqRXKpBW)7h)9wX@V>?m4UHI7Q9lc<_377zH8oSQ=&hb3k;|Qlmt7qI%FSn?5wNuOIcy9Yhzrhf(#}RDB*D z5l-Qhl7VS7IDZ!1_MJyNmlsf3!xHKdzk<%&t)c2(Qf%QSj@t!nqr`{*!*QvN0=4NYNBeUs z(R}Y})PJ!CU8}7_Q-n>Z^FT8ix!i$TH*}$WaouR1T`$_~KZGW)4x>&@qiDHk5{(#{ zLL0a3v6qwB;ea}(i_vixXH-7rf`*m5p{g(sG&Cy$-Sv>7%DE^st2zetii$%QtrO7t z-b6G$Cm9uar=p_^X=rI}1{xBRiS7up(XRd+G&3&`Rrj~<_O$qMaiuuL-cF8=3@Xr) zf(rERl>~o#r5eXO8*5QnLOtpsYDDLUo6y>#7Bp7UhT5&Sqr=UeXmOGf4RY*3w?=!> zyZvr{M$jOR$2d)(wiA>4{XBTq{nyP^bZi(@EOEwVCLS|-B@yO%eJ9%7j)}@G!Zx|> z%Vv2#yrK+VVbag+Y8byjCM;XmS_+*`r1JTYAZo@MW@Aa}4EWS=oSJ#dOU6}u^lqAN zY>H)jn3nW*Lx!#(fVJ)SK*GM#}T zBilV=&ly%ys8&Z5%iWr@9Rs1St+`Oq_9jCv#?~u(ljZk~Nv)#%sHH~OaiPMqNPY!r zDI^Oq%uL3iu#iXB1I)tOICg86*5&sv4jP8k=?y1X^hpntE$EL+j&IfKgt$HA?WZsG zpgL8Qa);&r)${~J#DhE)3O^ZE(7B;Z^ORnFnodsSecllUJH4jTPEMr2plMiZYLKjm zjJ=1Lp*}57m3fVW`Ccx|tAf2%&+JaK)*_jnKDPe~t255LaOqfUlSNjaaZ#IDw5Lhv z62lkjS$kLp+M7UL7TMo7rQS;uEhZD{*B-{Fcq6QQ?JaxjxQ0u33E<_~_|;Fk(% z&+pB1ch_DNMxCnkCEFrOP(@k9rd~-vf1{ zI<~tUk5UU^td7c=32HyG_>p%s%V(8wkh0chn1lt!HJs%#%5simEpKbO*_up8>g>3( z_#LKOC5H2(yvjCuxt>=fV=ws~aG%i(_BQLQ;WeqK)Lh7#0cAY6mOxq-*>-?(FOt#< z5w+ZwSaVSZyUkwPy_xDBXFCP*=7l`lEOtRY<$8D!M^8;JC97bU<(15?v1e+O>@6QwXfVfa!lY~d^m3qf zSet&AyLJR#`@*w6##ya-q+pv7|FUD|dQ)pKRh~_HwETOu(~h|Me7<1q3(7f;^B?__ zqK*7I*e;hB2d)pD2;bvYGMP4<_H!{C?BcH%J&h~Y>FL)aUfF1QK(J$3lCI@s)IM&} z>Iptc?o4HsVNCoOoswBa#TDt>J(gf+Ap0`COKbcA+bv$=AInFMZhY;yC8ZN(nv+wO z8x5rWPjPmz+{~W5wc38)h_il5o$h!LTlKVXhuiaFmVi^71_9NYL3XfH$&0l+C>)E0 zF#&8Zwu7WsKceB9V)}4x(Fs{#B-NT=4YmsNEY*Ga#agME^P1(i+uU{PY`KCxZFimi zMXqvy{H`|-^5$&Gz`)_YdPfrC&Rae$ z7ISO5FK?!3uSe2BZd$gZWZh9m5Kj4q=r>P#lJG*V>LZa4x4&Bk11mghv!$dvAsK&& z(RXUolpD|Thp1>N+gqnw%oTOS(R(=@`}>_Y*_E2R?c6eUdmJ}#m*uf{uEUp%_UPDN zw4eKHZd5ibs?1z%8>sbKF)R#H170vuK$U!E`{S{j_BTC#l&vY;vMBU%yDzIPnA!{? zwee)47}B-Z(l(gFlM=1CB+b#5F5Xg^n6p+#hZO7d{%)WSrdBOt=SkfbY3m^Yt|U=F zS8S4)8M3`eM1zzcAC{i$DunuDuG^635kXW#mW$agFwzPt8zNnf*OBzSQuTzFw3}&o zu~kg*cSx6jQqGgqC6ZfC#V*pa6l!u7s-?d!@V+o~L9Qx;;?dJ7zEG+^V34jIpQr8q z)XdinSLP3{TUfu$x0-)YmH=@&xh^1mEKYSOVa=a77J)3Bn?1tu&89`6u=aR;I4Lss z*dB%4%5=Smh>H!LP`wfb*@8{tVFOKPTEB47-4=k&X9-!PX$rP7Skh34RFT95cc^Xq zd8~-8^t(3qjiP6Q&Z@yVB^A(_ybCQ>_Ir1_yugpPJC#Uh|GMQ;`7PH&{Wq6*MV94X z+rQ&+se~tWCq<9iAD^TBieN)Q@p~y{4wYt2fk*Prwo|9Z$oXkv>_8r>U_X}moE_ma&wco0 z7CfBHQP4fq2AuIGM>^nfKk{%Hac8X@x-n-~OgVEEtYOCCMzsDc6~gK!*e+g+B?B-{ z^cUdzFj&?Q8O)Q1UKW#g(&-6~EqTnI)n)EW4oiuM6+zX*81aN0Dgzp5k27)PqAw91 z3?X{GI_ZW7Jcv>J)q2p{0{QhmFd78TJZSbq4#+r9Ra5un9EdtlPp3ZAW%TXy0dVoS zrNwbh7_~l5jXwO;SLd}xUN+T^An{_D{4|9W9=QE;CpreAz#D$A02w4f7?irel*nk;|=yms^_2j(mXa=?T8NUKHNncGJ%^G=_;cDoW@@PkIvNdrId z(R^t5$sY5y(=Qc5DWpRru3s+7HFPqUU+*ze z@$xLUIj^|!j?ZXj`NER!w2t0~d60)+!3Q$&pNN?@QBl_ibpi>75#d=;8{|v1G9{$J zVlqa@*@ZhdPKKzc8gMcdZJL&DY05%rzmH<3pXpA3wuAf@w(KYFnXpDC3n0;WDFref z?Y5?7-w(L0JR;m_g7tc)KuAp(#7Ws<*V2UMAzi$65ogGaUENNLPbZf^?agKh5t@bP z!PbrKX^yoWG)Z~bX$Wwj+ulh1(fU=D1B70!FW@E4Gbu@QY6wX@Z*4=OFGx~g{f&iU znE8=+F%dbk63P#S)q#H#*KUD0w{S{aIP0x+=HmUcdz#HnWTuenk0kvod}^3aDUzT% z4-!*})0LMmC6iH-)P zLN?ofz*trUGqk-8;m_*yCP0@CHbhHA8UsoyCKP5x(A>fvoQD-_mRl&qZy!vv(MsrI z&8`+eqf}e=P2F#EY)H~WQRn?%s4$&4zeI}Hz;o)Ci4kXK&Io!*uK<#-CI^}e9ANjO z!d2Mpwv^7BOLJLm(g_l?K=u+Lc7@DukezrU-3I$iu!|&p`GoIAa)XFp5=jXG*`Yiq z=>4QC0vcj3G-Yu-JoVf*VQm@w?2ioDa|SOewv58gZ1j?8U(zTggT5rzft1&hnIckL zLn0fAs#CXCkU|y@_phEEvDR-DQS)@9xssAcavLlwpARx`;FfrS`zNmE6Xr9|4pGiF zV0&~th*G8-Ik%HkB~`yk#y5!lEQ~xaUIM#wots7~kK<$Mn%*zWY?>9iX+}CwoMgt-&-r`lPyKQwT}S>| z7F%^7oU?tj@qqAf#>thZ-Sj36=ImhhDapY^-t4z!4@_OM=GOVZd?;OHO-tm|Kt0u% zODVglY%#TAIBFsYFpYA)TBCX@R(dExaMXKFQ}#-RyJo_(lXm+4rH9KLwZ(Zlz1;OO z;8zpJULtBCZX0Bxiv(qnoGtd&yvevHllWHs6wfw|<&B|Cc=lwkK`a~|&~!8UhUY;J z3>Xe+&X9+P0ze5Sxg7I$;M9UiHJlbv&!0XbJiJGF5{`m(cIjjTwQL%w-%1_d0;>vQ zI0zTg*bmp3_d*nX`{z$4!SzH=6kSTWlQVO0qy!%BgKPdIlPNHNqF85^bg*`rU8>(o ztnEOl=rj@;4A0F$J2ViTdbnHz=I$gG^2pVvhv4l^s*v3Umzo)S-a42Cks@k`ltLM? z1sf^=>S?c&B63fMhEs6Yq;8~8K| zTVE&e!nW=?tLMx=ayv1?8Q1;#i-lJ+R(eQPH?#_M2fhU4_ zpJ?gv(&;0Y4{IGiX>9U1?;(?QD%bSgbh3yqdEwx%3|9?fUy0>Dy=dxc^4UQv?tNBQ zUTB4vf}qnR%)l2Uyc-*bmJJgQCF?()#(R?1r!BP)TAtw=oO<{G>((J7)+_D!bCp5? zUhW6Gy|Sb*BsN)`Y;*NK!K~5gXfBJrpUORe#}Fw zk=)%E{>$L^zZduyx4^ww0_vZqFz?Njo%>siUme%KHwJ%itX&=LR!5`NQD=3$S)Jih zM{U)ST6N4-9Ur|nGOCV!-W$4z}Nl*LFBnk8Dl(r|)P zSClM`am@j8&^_wT+B{Sw(foHT#}T3Z8%hNdpjAOMsG z4}dT%{4|aRt-0UhF}(Z9Z+e{)A^1MMei z9Rm1|$7XaX!hoedq1hS?&1e&#Ihuw9IG|-p1I02>ZvcGR|I*{{JNf?ezm$ysM_#$g z6kh_<`(+2f-lzSa=aq35Zn=M2i*WDZWAnE}nL3?y@8R?P6qY(|<>Z@$Nt=6V$?miSa|}h?OQ^43K|6`JhfRsM8DTgIb*yxHlp2LrDO2IA0xxR|nMZ4WHi|Apd@7 zT-`YQD*^15m<9|&-y6oR@NLH_KNQUN8|uTks|5q-_XFFL3FA2Z-Uf$XJcCnuC39%* z`aJ5}vWTuEEu&42t7y{L8Y(W`KqrE>P{rmp8s4^x?xpOZJxMip{m)!i(hM52~p)wG4hzv%@_CnCI?l4pxbpOu->h}iG|6WjH zQ_*TZ-^p@%&A!#JrtYx;R{?~-Q|Ib`w@e|pD;;_&!MT9tGevp1+QZi5!TvU_W(Tu6 zXT9VdZT}`tsaSiUhf3W3N|pwVB4^0$cLTn=2Xt6deLbyo5C3@Ek>}$LLyeeNKg{E4hley!S4>==WN>q;LPua z(sfU8b1sLnf>xNW{9Rqm&4IFKy<3D(T$To23*S2X3t=X?66Wx2`fb8NG*bhi__nT& zRnYF-0^^%)Fpu*((>`jGzAkOg0e-}-xE#(Xh6|NY5o%etUxQ_%vP>`;?1!E>D7-OP zO+}YNV+K))uG>j}e`@nYmhpUxrt8^}AWlInk>?)CZK8VJj}#sfYUPa_oM27Wu$}$b zt+r;_WYWk%Vw~!$pX^K9{xPNVKf%Z`2#*wGHR^O|5IxH9FgJAj{$>EHr3Tup3J7127e|U8O9IK(<9o8ds-Mk?z z3KlwboV{9L(pj^+m0Q0voJ*Zg6X6HwnI!Hgx9!0XR+3FETdd!jrTq`J#FYAekV7;4hj~I z<;BzCQV>2c#0d~U!2Suw##HykH%`RC{l6CJKOR7i? zLvxj^dg_Z~vy6oSm=tS0zIK$m z_K_1>rXRM_Q<|Cjhi+d!$2&C3Ea?qvfA-!}Li)~yYC7AhkZQmle=<>+bhF0vf_NWf zfii=ZhCaNwkb{5{kR(`#Q4)A?*d&#TB-12DCxPiU478AQByjaU)^m5iio3b| zknc~%n<)8>1V0jUztR=j1r~6y2s-Xz2>FaT28xA{#5Hl zp<&f`QTwHCHs-qT{PuV{ocktKH_!xhc-_P-oIKjXTwY+YIlgRWl&&N7ELzK--9-KM z{$iH7796hOUF7|8+u;1EN%pI$@TiNXKP=P4j;cLau9HDSEH?nI(sx+rct_9an|@nH zuW^(0PJa00C7qKAta1G@ZFl&~kHYAmUap`Ut$L{r!Y7qv^*{#OPCt$JRRctjCh#Dx z`k62W*)RkNR5;1SDQiS}XwtZqQjlFTtgT?yj6%%^IFay#75An6@<ZJbkmajZak^g@!BN?Q!frmF*WhVRNRX*I{vzS_os)!;xZ{4f3J1+_W_bO$%javZ>;-&nx5+e6RWyDDqd0^Jy5|O9XKuD`o?ehNJZ+M#8@VDn^PcWluaP2 zVyRLuI4e9Ml>g@Z+X4Mo#~X|*L)gV`tYPt^Tbs`>Zs_$#9Y{?ybW>hm$=4|r=q5LC z@`st4TrJ;zZb#EaC+o{qrtbUohEF76rRph-S2Y^x_6ag{Brp14+pINYXkH02aXYLr zH~MsC_R(G;WGsQZ0GgeNQ~+gvZvL>)6QplBnT3wfIh?&Gy$guFiQRW85Hz9HY&m;= z=W2EZnGU2oy5OOH2~pkRv+ao@ja0bNvMc%8`H#=W(SFTbmzyz;@M_43Qj>0Pu5aw? zj&$RGHSZI6K!OHd1-^#|tb-l{932g@PABV~KfL^x*Pgs_$BJD6%;`_Hp9L49qi-rE z67pA#Z;rE>K&S^ZE%SM31oQLcxYjW7)*|~?Oix5;W3U=dd1;;MVlC;$e5EkRbEk4# zbSsEPBd_JK3r`VdDp}|BRuK0ta4vy{R#*y#?f}@p_+k`9-tN>YXn_{3?5m!AAUB@M zhd%@@K$yvvMdn$j9;kPqla;`y>KdW_#T+I961TC-NT$#B9TQ(udJ$`PYV};of1FA% zuGN>hQhl}Br-HTj*Ncr;x-6jC?^|pTTWz+6rUB)lyUcq3FK>w8&WksH{vb~YEUVA- zeneb}fq{jDgc31;Em(2s1n7a|`j&eb$vS)u?$?_mBV5=o3x5KK|L)`7hUmY(ScRAV zUAOsvtZwt)6`TKg#pd^e1^=$z{CD-{zpFR@->%;Lez)_7!e0M+z)KyX`q#r!>Mr8{ zAGMt?IW91R!sB)()#4Mi8^ussqJV+rSZHg&>q$ipq`DQuY@`H|R$@R=l>@H*ji5~a zd^9E(Vtv|SyRsU>gmn-k$bhY(co@O1tw<0K^2um$+=+y+v}ACxDTlC?d?@s+fYG)v z@bifT7xA~^<=YiIa`1Mqhn_+Sc>7=UuQ{4GlMkZ4JjfB}K!GDBYz0~1won0rof=r# zN`vN9ERe9zf!^-N`JxbrsA_@jbOl5$@mx0z)>Cx5=XH90l3~av8iotKKprcDc)uj@ zUy(y%dOr9mb6`N8h7p)_*cwj*TTwF1w1z@$jW={o6hgdp6S%HqL3MN(?D;0bjJyEa zBJ)7tTneJB3fQzxf@yCBtSG%fg{vx&bVA}7hTyvDAg3}G^0P|8IxhnD#90tz9}0gShg8rm3sMx564bOOJDSS=nQVMgKjZi5l2VrJCl$2g`4f>7$WAm35v#rEsbs*^} zhC%Nlh^Ui8%WOVm*jItOZ654&<$wbQ4iYPqpj+Szo}oz)9i9qyxdpJhnhz}^8RX_S z!R$&kgg2&xv^5M`w;vDM=zz4vU}z6Xg1U$tn6GSw^!R3|TquEg*F2a|I)Jn;58{H; zV9dJ$=DXuyDlG>BmrFnuCx(`-Vo**sgKQW#M^6R_lM^8|G9Hxn*`SihAf*jUSH>bi zGL#0vczskOlY(_&5==_+pk_1$A}7m0VgDzmn)fB05||pW+VQzKA&!Mn+Y-pAwJHk| zLO}yJv+^g&WqFX>k^?OZ*)TVo3R`Zu(A1a+G7n!Uiq8daK`vDF<-$N~EaU{{ff_8gBtr6lTdQnju$I3X^RKFqP{8 z{7oB3oA|cdCm3S3GQiVI2BmII&=6b$CF36jthvHcLKW=dO0^_4K_c!XIc^H@&5nWi z!IE!8wzmujN@m3^Hef8jika-xhKR9Vd%`J89W2aU}v}t`eU-eHlP|3;>#ev zEfrF-aTgt~g3inwSj@_XvcUxC4-$hY|F2DTZ!WD}i`mxSOfm^>F=jW(XM9|*xA)#3-t|m~ z?`_uxswVgSeA{YBYN!N$Egqqs!LaI73%Rv-+fg!IF2=QtJN8FU5t=YiGy{$IA2_h=GAfc@W;#bRHA+!>dzGWZ?R=`M! z9OiQ>F#1{uDFt|tD8hjsPy?ew9?*^Dkqa_R=D|y)$xwJa!htALNSqtxTE`j-XP5q2 z)eJLVX2o+I)@X?fSt*6=DgDek+IJeZ+E`u!i2t4YHE$nk=620C6Y+3YKEU^ZWCDs?fJ;q zwVXSi7gDV%vSI0FN0DYyxPEmnQyotn(%JqBrrtrbPsykTiB}=U`3eSLy^!`9BT_pi zHH_u<%I4R>xv=;v=||C@J9)6y?adqFc%_Z>f}AU;Hf{2vgWE`})TpjSOA)I%)C7Hf zv|Sl@){S`lzCgh!189FgN_4G1l#;!`|EcK3z#(W&fQU7=R~^ihfNve+?rbR^KTsTQ z;up_e_Bc@QM;bCHzeQ+$r1h0hv%=qp-RXK}uM?sJe^ixmI5+^39C5FwCSI*|BQB3+ zSk;MHv2ylUoq?c@%+}K0)zn-UnV(?|z2oZ(i=T|~q4E!#Z%%uF>w5wvNQ6v{7d5y{ zd#6*w)1-CC(9cWDZ&ka>nUy?C9D5E-=99E9_@hRvynzo3{bA&*iEp~U8&8CmC@A`% zBY+h88%I{?l;n_}3HH#4ff&m~q$FBN(j7_g2JK`^_KIlvIGJrFVX07cr|1)pUqn7v z`>y+oV=uQBkjgZ@`T@4Rl%29l70=Obmw4{FA#{xr^V4;8+9d8|h(H;d&x%<65X`dxW@o7%&Z=2a=& zP8E^1^7`E1)l!qFvkLR^vm12F`BT`G$nqiJjX&6K!19%hWBwxI-A~!&Lgo@UzZaND zVkaq$?kldp?EFRWhbuM^^RvNkLOv;bX+9ddW)EZu4*7NSCU!a5p}O6x#17ZKg^ek) z&_Ko~)i^6ezu1?%R?Otb(ZwmyI0ow};1dSXi!k!_rmI$8la7D0W~nP{+*>yzjvn8? zo8+YvycZr>z_9H$Jbd#}G3`jo;ME~20{*-PFJ8azowGJ@F$PM=6x9G(B${%BH$5p= zNYSl@(`)d-9fa={o<`&%7Km{e;FeAB$o0ge@?@@x*v=r z&9I6wreFQh6~6rIAEL-6Cy;U>6C{*90pE47HZj=zNi=xUKY2m{3-HsEAD=pX)(FxyLUhTMHDE=HUyD*=!HsFGv&*ua5c7>PhSyH`kx8lSD$|Q$8SE3gebVe<3LNk$*w`ZwL+7B*1 zXm!Tw;v>sD2b#}0rNEQ((Z9P-LYoaJg!WGaS2#2gUl7eKK1!+P$tb+K|dda&lZo7F*yX( z1Kx<;kpS5r4BwP}xOm6zH&L(2#Uyr~$_Qh1J5uEyoRlplBY||`5@&N5fR`WO%Gi9QnGg~Pz6(r9H9faNFC~$zL}sIn z$_}G@mDKnO>22XW@`sg|1x-wJGbQ(+8v4mhAWX|(D1nFWM@*pYxxI!44vN@WC1 zbZa-2u*gIRsYVxYZD#iD7@I|+%Ea66!C%RQM8m?D!J$MrM|PdS@rB^8SMT)RcKfvU z?ZK}`Z0L3@X1x?FB*{yfLG~O6KW>37dmHOydcaymO3NYXHUIav^jbf#f2H|mM-Vhb z=_O5S*}6i%0=%AIJ1ljAphxC;!DWooC@a>U9oRz659`Oe&^^->Kc8w3C266g$A`F% zfNwl0T!7gLvg=1mr-_#%QyxaG37Oqos=kg=EbJj?pJa&0Y$BNmAoW6m%~n7c$@Hg-q;%T`iMC;8stlC@tho#j+mpaSknO;3 z57>nhRRGCbfrw{9&rE$O`DN!{%0Ev$+iDA$F~lZ=?Kw)ySE=59N>xqj9B90$DP2xH zZAr`&84}ZLQnHB&^BgiDr5t0SvhC5k9*#j{A=sGg;}H^lokp|Mm|?QttpXF z!Nc_ICImIHTVgr8quL=URHq*m*iA~`&*WG$1)57b3wqu}p`{UrcODGD=B>B$jt83* z9u3Qc=6+HY0fF(5^PQ7B5mi%x)| z>EJ4IIG&rtA(5GDXOCOcUQ;A~o+I~VIaIQ8Bk19D%2P(XT}YM#>61cFG--Dvj{eNp zGTW_+-B-huF47(D;Dae9cF z0pyTTsvVmP1?2{Bs)#ofX^(;xpM3GkGfzGK(6yI0$b0rwG8B+M-Z=J3J2gpE;7tGi z&yW84+RdAPEhh22F_KEnP!3EW-kN$uF{4<(w9#obhI5 zk~p$R#}i-T&5B})m|7wxnON-_3@(s?N%l#)on1)VQcAqX<8BlE-%Tz2`}P0x7T8a< z{C)U=RLlMgeqh$U>EQpZ|4ql`Y*R9cI1~58^dmXu-4}cIYYjQ;T=`+8_SU5&?QpI= z<~8>}g~OP#EqL_(`|HYyr{((POte)ICxYQ$NZhZ8 Date: Tue, 11 May 2021 09:42:50 +0800 Subject: [PATCH 206/232] Update mobile data preference getter/setter To satisfy OEM's requests, setting mobile data preference should be by uid. Thus, update the getter/setter to return/accept set of uids that need to apply mobile data preference. Bug: 171872461 Test: atest FrameworksNetTests Change-Id: Id44efd2a6d820867f4405426c91e65b9ef155898 --- framework/api/module-lib-current.txt | 4 +- .../net/ConnectivitySettingsManager.java | 42 ++++++++++++++----- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 524db18005..b219375aed 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -57,7 +57,7 @@ package android.net { method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context); method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean); - method @Nullable public static String getMobileDataPreferredApps(@NonNull android.content.Context); + method @NonNull public static java.util.Set getMobileDataPreferredUids(@NonNull android.content.Context); method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context); method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context); method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int); @@ -77,7 +77,7 @@ package android.net { method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo); method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean); - method public static void setMobileDataPreferredApps(@NonNull android.content.Context, @Nullable String); + method public static void setMobileDataPreferredUids(@NonNull android.content.Context, @NonNull java.util.Set); method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int); method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String); method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int); diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index c4bb2d3f54..07754e4af2 100644 --- a/framework/src/android/net/ConnectivitySettingsManager.java +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -28,6 +28,8 @@ import android.annotation.SystemApi; import android.content.ContentResolver; import android.content.Context; import android.net.ConnectivityManager.MultipathPreference; +import android.os.Process; +import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; @@ -334,12 +336,12 @@ public class ConnectivitySettingsManager { "network_metered_multipath_preference"; /** - * A list of apps that should go on cellular networks in preference even when higher-priority + * A list of uids that should go on cellular networks in preference even when higher-priority * networks are connected. * * @hide */ - public static final String MOBILE_DATA_PREFERRED_APPS = "mobile_data_preferred_apps"; + public static final String MOBILE_DATA_PREFERRED_UIDS = "mobile_data_preferred_uids"; /** * One of the private DNS modes that indicates the private DNS mode is off. @@ -1002,28 +1004,46 @@ public class ConnectivitySettingsManager { } /** - * Get the list of apps(from {@link Settings}) that should go on cellular networks in preference + * Get the list of uids(from {@link Settings}) that should go on cellular networks in preference * even when higher-priority networks are connected. * * @param context The {@link Context} to query the setting. - * @return A list of apps that should go on cellular networks in preference even when + * @return A list of uids that should go on cellular networks in preference even when * higher-priority networks are connected or null if no setting value. */ - @Nullable - public static String getMobileDataPreferredApps(@NonNull Context context) { - return Settings.Secure.getString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS); + @NonNull + public static Set getMobileDataPreferredUids(@NonNull Context context) { + final String uidList = Settings.Secure.getString( + context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS); + final Set uids = new ArraySet<>(); + if (TextUtils.isEmpty(uidList)) { + return uids; + } + for (String uid : uidList.split(";")) { + uids.add(Integer.valueOf(uid)); + } + return uids; } /** - * Set the list of apps(to {@link Settings}) that should go on cellular networks in preference + * Set the list of uids(to {@link Settings}) that should go on cellular networks in preference * even when higher-priority networks are connected. * * @param context The {@link Context} to set the setting. - * @param list A list of apps that should go on cellular networks in preference even when + * @param uidList A list of uids that should go on cellular networks in preference even when * higher-priority networks are connected. */ - public static void setMobileDataPreferredApps(@NonNull Context context, @Nullable String list) { - Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS, list); + public static void setMobileDataPreferredUids(@NonNull Context context, + @NonNull Set uidList) { + final StringJoiner joiner = new StringJoiner(";"); + for (Integer uid : uidList) { + if (uid < 0 || UserHandle.getAppId(uid) > Process.LAST_APPLICATION_UID) { + throw new IllegalArgumentException("Invalid uid"); + } + joiner.add(uid.toString()); + } + Settings.Secure.putString( + context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS, joiner.toString()); } /** From a869887d4f709e72d4dd321afda8f31cf224e9f1 Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Mon, 10 May 2021 00:49:14 +0900 Subject: [PATCH 207/232] Do not automatically redact TransportInfo objects. Currently, NetworkCapabilities always redacts the TransportInfo objects it contains whenever a defensive copy is made. This makes it impossible to make a defensive copy on a TransportInfo parcelled from another process without redacting it. Stop redacting by default; instead rely on ConnectivityService explicitly calling NetworkCapabilities' redacting constructor when it returns a NetworkCapabilities object to an app via a callback or synchronous call. This is currently done by - createWithLocationInfoSanitizedIfNecessaryWhenParceled, which is called from callCallbackForRequest, getNetworkCapabilities, and getDefaultNetworkCapabilitiesForUser. - getNetworkCapabilitiesWithoutUids, which is used when sending ConnectivityDiagnosticsManager callbacks. In this method, unconditionally redact all information, which is what the code did previously due to the default redaction setting for empty NetworkCapabilities objects being REDACT_ALL. Bug: 183938194 Test: atest NetworkCapabilitiesTest Test: atest FrameworksNetTests CtsNetTestCases HostsideVpnTests Change-Id: I3108ee94cb0930958e071ba678c3554525b0db82 --- .../src/android/net/NetworkCapabilities.java | 22 +---- .../android/net/NetworkCapabilitiesTest.java | 2 +- .../server/ConnectivityServiceTest.java | 91 +++++++++++++++---- 3 files changed, 80 insertions(+), 35 deletions(-) diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 4a99d290f3..c19a906ecd 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -139,19 +139,13 @@ public final class NetworkCapabilities implements Parcelable { */ private String mRequestorPackageName; - /** - * Indicates what fields should be redacted from this instance. - */ - private final @RedactionType long mRedactions; - public NetworkCapabilities() { - mRedactions = REDACT_ALL; clearAll(); mNetworkCapabilities = DEFAULT_CAPABILITIES; } public NetworkCapabilities(NetworkCapabilities nc) { - this(nc, REDACT_ALL); + this(nc, REDACT_NONE); } /** @@ -163,10 +157,12 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public NetworkCapabilities(@Nullable NetworkCapabilities nc, @RedactionType long redactions) { - mRedactions = redactions; if (nc != null) { set(nc); } + if (mTransportInfo != null) { + mTransportInfo = nc.mTransportInfo.makeCopy(redactions); + } } /** @@ -175,14 +171,6 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public void clearAll() { - // Ensures that the internal copies maintained by the connectivity stack does not set it to - // anything other than |REDACT_ALL|. - if (mRedactions != REDACT_ALL) { - // This is needed because the current redaction mechanism relies on redaction while - // parceling. - throw new UnsupportedOperationException( - "Cannot clear NetworkCapabilities when mRedactions is set"); - } mNetworkCapabilities = mTransportTypes = mForbiddenNetworkCapabilities = 0; mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; mNetworkSpecifier = null; @@ -211,7 +199,7 @@ public final class NetworkCapabilities implements Parcelable { mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps; mNetworkSpecifier = nc.mNetworkSpecifier; if (nc.getTransportInfo() != null) { - setTransportInfo(nc.getTransportInfo().makeCopy(mRedactions)); + setTransportInfo(nc.getTransportInfo()); } else { setTransportInfo(null); } diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java index b178bad712..d74b802c87 100644 --- a/tests/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java @@ -340,7 +340,7 @@ public class NetworkCapabilitiesTest { private void testParcelSane(NetworkCapabilities cap) { if (isAtLeastS()) { - assertParcelSane(cap, 17); + assertParcelSane(cap, 16); } else if (isAtLeastR()) { assertParcelSane(cap, 15); } else { diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index ab50798206..221cc9b26a 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -19,6 +19,7 @@ package com.android.server; import static android.Manifest.permission.CHANGE_NETWORK_STATE; import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.LOCAL_MAC_ADDRESS; import static android.Manifest.permission.NETWORK_FACTORY; import static android.Manifest.permission.NETWORK_SETTINGS; import static android.app.PendingIntent.FLAG_IMMUTABLE; @@ -9407,9 +9408,9 @@ public class ConnectivityServiceTest { @Override public TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) { return new TestTransportInfo( - (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0, - (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0, - (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0 + locationRedacted | (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0, + localMacAddressRedacted | (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0, + settingsRedacted | (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0 ); } @@ -9432,8 +9433,26 @@ public class ConnectivityServiceTest { public int hashCode() { return Objects.hash(locationRedacted, localMacAddressRedacted, settingsRedacted); } + + @Override + public String toString() { + return String.format( + "TestTransportInfo{locationRedacted=%s macRedacted=%s settingsRedacted=%s}", + locationRedacted, localMacAddressRedacted, settingsRedacted); + } } + private TestTransportInfo getTestTransportInfo(NetworkCapabilities nc) { + return (TestTransportInfo) nc.getTransportInfo(); + } + + private TestTransportInfo getTestTransportInfo(TestNetworkAgentWrapper n) { + final NetworkCapabilities nc = mCm.getNetworkCapabilities(n.getNetwork()); + assertNotNull(nc); + return getTestTransportInfo(nc); + } + + private void verifyNetworkCallbackLocationDataInclusionUsingTransportInfoAndOwnerUidInNetCaps( @NonNull TestNetworkCallback wifiNetworkCallback, int actualOwnerUid, @NonNull TransportInfo actualTransportInfo, int expectedOwnerUid, @@ -9462,7 +9481,6 @@ public class ConnectivityServiceTest { wifiNetworkCallback.expectCapabilitiesThat(mWiFiNetworkAgent, nc -> Objects.equals(expectedOwnerUid, nc.getOwnerUid()) && Objects.equals(expectedTransportInfo, nc.getTransportInfo())); - } @Test @@ -9483,6 +9501,40 @@ public class ConnectivityServiceTest { wifiNetworkCallack, ownerUid, transportInfo, INVALID_UID, sanitizedTransportInfo); } + @Test + public void testTransportInfoRedactionInSynchronousCalls() throws Exception { + final NetworkCapabilities ncTemplate = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .setTransportInfo(new TestTransportInfo()); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), + ncTemplate); + mWiFiNetworkAgent.connect(true /* validated; waits for callback */); + + // NETWORK_SETTINGS redaction is controlled by the NETWORK_SETTINGS permission + assertTrue(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted); + withPermission(NETWORK_SETTINGS, () -> { + assertFalse(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted); + }); + assertTrue(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted); + + // LOCAL_MAC_ADDRESS redaction is controlled by the LOCAL_MAC_ADDRESS permission + assertTrue(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted); + withPermission(LOCAL_MAC_ADDRESS, () -> { + assertFalse(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted); + }); + assertTrue(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted); + + // Synchronous getNetworkCapabilities calls never return unredacted location-sensitive + // information. + assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted); + setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted); + denyAllLocationPrivilegedPermissions(); + assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted); + } + private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) throws Exception { final Set vpnRange = Collections.singleton(PRIMARY_UIDRANGE); @@ -9840,12 +9892,27 @@ public class ConnectivityServiceTest { // Connect the cell agent verify that it notifies TestNetworkCallback that it is available final TestNetworkCallback callback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(callback); - mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + + final NetworkCapabilities ncTemplate = new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .setTransportInfo(new TestTransportInfo()); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), + ncTemplate); mCellNetworkAgent.connect(true); callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); callback.assertNoCallback(); } + private boolean areConnDiagCapsRedacted(NetworkCapabilities nc) { + TestTransportInfo ti = (TestTransportInfo) nc.getTransportInfo(); + return nc.getUids() == null + && nc.getAdministratorUids().length == 0 + && nc.getOwnerUid() == Process.INVALID_UID + && getTestTransportInfo(nc).locationRedacted + && getTestTransportInfo(nc).localMacAddressRedacted + && getTestTransportInfo(nc).settingsRedacted; + } + @Test public void testConnectivityDiagnosticsCallbackOnConnectivityReportAvailable() throws Exception { @@ -9856,12 +9923,7 @@ public class ConnectivityServiceTest { // Verify onConnectivityReport fired verify(mConnectivityDiagnosticsCallback).onConnectivityReportAvailable( - argThat(report -> { - final NetworkCapabilities nc = report.getNetworkCapabilities(); - return nc.getUids() == null - && nc.getAdministratorUids().length == 0 - && nc.getOwnerUid() == Process.INVALID_UID; - })); + argThat(report -> areConnDiagCapsRedacted(report.getNetworkCapabilities()))); } @Test @@ -9877,12 +9939,7 @@ public class ConnectivityServiceTest { // Verify onDataStallSuspected fired verify(mConnectivityDiagnosticsCallback).onDataStallSuspected( - argThat(report -> { - final NetworkCapabilities nc = report.getNetworkCapabilities(); - return nc.getUids() == null - && nc.getAdministratorUids().length == 0 - && nc.getOwnerUid() == Process.INVALID_UID; - })); + argThat(report -> areConnDiagCapsRedacted(report.getNetworkCapabilities()))); } @Test From 823f81c36e514e970f69609f0d6b4544a74a6867 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Tue, 5 Jan 2021 08:40:09 +0900 Subject: [PATCH 208/232] [NS04] Introduce Network Offers and their callbacks This patch introduces the concept of a network offer that providers send to Connectivity to register for relevant requests. This lets them see only requests that they can hope to satisfy considering their capabilities and score filters. This is meant to replace the filtering mechanism currently implemented by NetworkFactory. The reason for replacing this mechanism is that the old mechanism does caps and score filtering on the factory side, which requires these two filters to be contextless and available system-wide, including in separate processes from the system server. These constraints severely limit and complexify in particular what the score comparisons may look like. In the past the score comparison was only integer-based, making the code duplication not much of a problem, but as this scheme is becoming unsustainable by spreading the complexity of the selection across the entire stack, a centralized mechanism is now necessary. This patch only introduces the new objects and has CS keep track of them, but does not actually use them yet. Followup patches will implement the logic of calling the offer callbacks. Test: FrameworksNetTests NetworkStackTests FrameworksWifiTests Bug: 167544279 Merged-In: Idec1fe8eb4ac6f562bf098e3dd470f11024d04f2 (clean cherry-pick) Change-Id: Idec1fe8eb4ac6f562bf098e3dd470f11024d04f2 --- .../src/android/net/ConnectivityManager.java | 53 ++++++ .../src/android/net/IConnectivityManager.aidl | 5 + .../android/net/INetworkOfferCallback.aidl | 62 +++++++ .../src/android/net/NetworkProvider.java | 153 +++++++++++++++++- 4 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 framework/src/android/net/INetworkOfferCallback.aidl diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 0a3e231237..1a6b37bfdb 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3337,7 +3337,60 @@ public class ConnectivityManager { provider.setProviderId(NetworkProvider.ID_NONE); } + /** + * Register or update a network offer with ConnectivityService. + * + * ConnectivityService keeps track of offers made by the various providers and matches + * them to networking requests made by apps or the system. The provider supplies a score + * and the capabilities of the network it might be able to bring up ; these act as filters + * used by ConnectivityService to only send those requests that can be fulfilled by the + * provider. + * + * The provider is under no obligation to be able to bring up the network it offers at any + * given time. Instead, this mechanism is meant to limit requests received by providers + * to those they actually have a chance to fulfill, as providers don't have a way to compare + * the quality of the network satisfying a given request to their own offer. + * + * An offer can be updated by calling this again with the same callback object. This is + * similar to calling unofferNetwork and offerNetwork again, but will only update the + * provider with the changes caused by the changes in the offer. + * + * @param provider The provider making this offer. + * @param score The prospective score of the network. + * @param caps The prospective capabilities of the network. + * @param callback The callback to call when this offer is needed or unneeded. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public void offerNetwork(@NonNull final NetworkProvider provider, + @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, + @NonNull final INetworkOfferCallback callback) { + try { + mService.offerNetwork(Objects.requireNonNull(provider.getMessenger(), "null messenger"), + Objects.requireNonNull(score, "null score"), + Objects.requireNonNull(caps, "null caps"), + Objects.requireNonNull(callback, "null callback")); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** + * Withdraw a network offer made with {@link #offerNetwork}. + * + * @param callback The callback passed at registration time. This must be the same object + * that was passed to {@link #offerNetwork} + * @hide + */ + public void unofferNetwork(@NonNull final INetworkOfferCallback callback) { + try { + mService.unofferNetwork(Objects.requireNonNull(callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** @hide exposed via the NetworkProvider class. */ @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index a7cb618f97..d937c9cd78 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -23,6 +23,7 @@ import android.net.IConnectivityDiagnosticsCallback; import android.net.INetworkAgent; import android.net.IOnCompleteListener; import android.net.INetworkActivityListener; +import android.net.INetworkOfferCallback; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; @@ -221,4 +222,8 @@ interface IConnectivityManager in IOnCompleteListener listener); int getRestrictBackgroundStatusByCaller(); + + void offerNetwork(in Messenger messenger, in NetworkScore score, + in NetworkCapabilities caps, in INetworkOfferCallback callback); + void unofferNetwork(in INetworkOfferCallback callback); } diff --git a/framework/src/android/net/INetworkOfferCallback.aidl b/framework/src/android/net/INetworkOfferCallback.aidl new file mode 100644 index 0000000000..67d2d405db --- /dev/null +++ b/framework/src/android/net/INetworkOfferCallback.aidl @@ -0,0 +1,62 @@ +/* + * 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 android.net; + +import android.net.NetworkRequest; + +/** + * A callback registered with connectivity by network providers together with + * a NetworkOffer. + * + * When the offer is needed to satisfy some application or system component, + * connectivity will call onOfferNeeded on this callback. When this happens, + * the provider should try and bring up the network. + * + * When the offer is no longer needed, for example because the application has + * withdrawn the request or if the request is being satisfied by a network + * that this offer will never be able to beat, connectivity calls + * onOfferUnneeded. When this happens, the provider should stop trying to + * bring up the network, or tear it down if it has already been brought up. + * + * When NetworkProvider#offerNetwork is called, the provider can expect to + * immediately receive all requests that can be fulfilled by that offer and + * are not already satisfied by a better network. It is possible no such + * request is currently outstanding, because no requests have been made that + * can be satisfied by this offer, or because all such requests are already + * satisfied by a better network. + * onOfferNeeded can be called at any time after registration and until the + * offer is withdrawn with NetworkProvider#unofferNetwork is called. This + * typically happens when a new network request is filed by an application, + * or when the network satisfying a request disconnects and this offer now + * stands a chance to be the best network for it. + * + * @hide + */ +oneway interface INetworkOfferCallback { + /** + * Informs the registrant that the offer is needed to fulfill this request. + * @param networkRequest the request to satisfy + * @param providerId the ID of the provider currently satisfying + * this request, or NetworkProvider.ID_NONE if none. + */ + void onOfferNeeded(in NetworkRequest networkRequest, int providerId); + + /** + * Informs the registrant that the offer is no longer needed to fulfill this request. + */ + void onOfferUnneeded(in NetworkRequest networkRequest); +} diff --git a/framework/src/android/net/NetworkProvider.java b/framework/src/android/net/NetworkProvider.java index 14cb51c85d..8f93047cf8 100644 --- a/framework/src/android/net/NetworkProvider.java +++ b/framework/src/android/net/NetworkProvider.java @@ -28,6 +28,11 @@ import android.os.Message; import android.os.Messenger; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.concurrent.Executor; + /** * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device * to networks and makes them available to the core network stack by creating @@ -78,7 +83,9 @@ public class NetworkProvider { */ @SystemApi public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) { - Handler handler = new Handler(looper) { + // TODO (b/174636568) : this class should be able to cache an instance of + // ConnectivityManager so it doesn't have to fetch it again every time. + final Handler handler = new Handler(looper) { @Override public void handleMessage(Message m) { switch (m.what) { @@ -159,4 +166,148 @@ public class NetworkProvider { public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request); } + + /** @hide */ + // TODO : make @SystemApi when the impl is complete + public interface NetworkOfferCallback { + /** Called by the system when this offer is needed to satisfy some networking request. */ + void onOfferNeeded(@NonNull NetworkRequest request, int providerId); + /** Called by the system when this offer is no longer needed. */ + void onOfferUnneeded(@NonNull NetworkRequest request); + } + + private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub { + @NonNull public final NetworkOfferCallback callback; + @NonNull private final Executor mExecutor; + + NetworkOfferCallbackProxy(@NonNull final NetworkOfferCallback callback, + @NonNull final Executor executor) { + this.callback = callback; + this.mExecutor = executor; + } + + @Override + public void onOfferNeeded(final @NonNull NetworkRequest request, + final int providerId) { + mExecutor.execute(() -> callback.onOfferNeeded(request, providerId)); + } + + @Override + public void onOfferUnneeded(final @NonNull NetworkRequest request) { + mExecutor.execute(() -> callback.onOfferUnneeded(request)); + } + } + + @GuardedBy("mProxies") + @NonNull private final ArrayList mProxies = new ArrayList<>(); + + // Returns the proxy associated with this callback, or null if none. + @Nullable + private NetworkOfferCallbackProxy findProxyForCallback(@NonNull final NetworkOfferCallback cb) { + synchronized (mProxies) { + for (final NetworkOfferCallbackProxy p : mProxies) { + if (p.callback == cb) return p; + } + } + return null; + } + + /** + * Register or update an offer for network with the passed caps and score. + * + * A NetworkProvider's job is to provide networks. This function is how a provider tells the + * connectivity stack what kind of network it may provide. The score and caps arguments act + * as filters that the connectivity stack uses to tell when the offer is necessary. When an + * offer might be advantageous over existing networks, the provider will receive a call to + * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider + * should then try to bring up this network. When an offer is no longer needed, the stack + * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The + * provider should stop trying to bring up such a network, or disconnect it if it already has + * one. + * + * The stack determines what offers are needed according to what networks are currently + * available to the system, and what networking requests are made by applications. If an + * offer looks like it could be a better choice than any existing network for any particular + * request, that's when the stack decides the offer is needed. If the current networking + * requests are all satisfied by networks that this offer can't possibly be a better match + * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider + * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded} + * is called. + * + * Note that the offers are non-binding to the providers, in particular because providers + * often don't know if they will be able to bring up such a network at any given time. For + * example, no wireless network may be in range when the offer is needed. This is fine and + * expected ; the provider should simply continue to try to bring up the network and do so + * if/when it becomes possible. In the mean time, the stack will continue to satisfy requests + * with the best network currently available, or if none, keep the apps informed that no + * network can currently satisfy this request. When/if the provider can bring up the network, + * the connectivity stack will match it against requests, and inform interested apps of the + * availability of this network. This may, in turn, render the offer of some other provider + * unneeded if all requests it used to satisfy are now better served by this network. + * + * A network can become unneeded for a reason like the above : whether the provider managed + * to bring up the offered network after it became needed or not, some other provider may + * bring up a better network than this one, making this offer unneeded. A network may also + * become unneeded if the application making the request withdrew it (for example, after it + * is done transferring data, or if the user canceled an operation). + * + * The capabilities and score act as filters as to what requests the provider will see. + * They are not promises, but for best performance, the providers should strive to put + * as much known information as possible in the offer. For capabilities in particular, it + * should put all NetworkAgent-managed capabilities a network may have, even if it doesn't + * have them at first. This applies to INTERNET, for example ; if a provider thinks the + * network it can bring up for this offer may offer Internet access it should include the + * INTERNET bit. It's fine if the brought up network ends up not actually having INTERNET. + * + * TODO : in the future, to avoid possible infinite loops, there should be constraints on + * what can be put in capabilities of networks brought up for an offer. If a provider might + * bring up a network with or without INTERNET, then it should file two offers : this will + * let it know precisely what networks are needed, so it can avoid bringing up networks that + * won't actually satisfy requests and remove the risk for bring-up-bring-down loops. + * + * @hide + */ + // TODO : make @SystemApi when the impl is complete + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void offerNetwork(@NonNull final NetworkScore score, + @NonNull final NetworkCapabilities caps, @NonNull final Executor executor, + @NonNull final NetworkOfferCallback callback) { + NetworkOfferCallbackProxy proxy = null; + synchronized (mProxies) { + for (final NetworkOfferCallbackProxy existingProxy : mProxies) { + if (existingProxy.callback == callback) { + proxy = existingProxy; + break; + } + } + if (null == proxy) { + proxy = new NetworkOfferCallbackProxy(callback, executor); + mProxies.add(proxy); + } + } + mContext.getSystemService(ConnectivityManager.class).offerNetwork(this, score, caps, proxy); + } + + /** + * Withdraw a network offer previously made to the networking stack. + * + * If a provider can no longer provide a network they offered, it should call this method. + * An example of usage could be if the hardware necessary to bring up the network was turned + * off in UI by the user. Note that because offers are never binding, the provider might + * alternatively decide not to withdraw this offer and simply refuse to bring up the network + * even when it's needed. However, withdrawing the request is slightly more resource-efficient + * because the networking stack won't have to compare this offer to exiting networks to see + * if it could beat any of them, and may be advantageous to the provider's implementation that + * can rely on no longer receiving callbacks for a network that they can't bring up anyways. + * + * @hide + */ + // TODO : make @SystemApi when the impl is complete + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void unofferNetwork(final @NonNull NetworkOfferCallback callback) { + final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback); + if (null == proxy) return; + mProxies.remove(proxy); + mContext.getSystemService(ConnectivityManager.class).unofferNetwork(proxy); + } } From 0312c4593d061abc7d8c664f8f452f031ab3560a Mon Sep 17 00:00:00 2001 From: Nikita Iashchenko Date: Mon, 26 Apr 2021 19:08:53 +0100 Subject: [PATCH 209/232] Move IoUtils#deleteContents from CorePlatformApi set to framework As a part of internal core libraries cleanup move usages of IoUtils#deleteContents from CorePlatformApi set to framework. Bug: 154796679 Test: m update-api Change-Id: If7037029026b6753ab64be09aa52c40e04d5c7b1 --- .../com/android/server/net/NetworkStatsFactoryTest.java | 5 +++-- .../com/android/server/net/NetworkStatsServiceTest.java | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java index f3ae9b051e..93599f3c37 100644 --- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java +++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java @@ -43,6 +43,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.tests.net.R; +import com.android.internal.util.test.FsUtil; import libcore.io.IoUtils; import libcore.io.Streams; @@ -71,7 +72,7 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { public void setUp() throws Exception { mTestProc = new File(InstrumentationRegistry.getContext().getFilesDir(), "proc"); if (mTestProc.exists()) { - IoUtils.deleteContents(mTestProc); + FsUtil.deleteContents(mTestProc); } // The libandroid_servers which have the native method is not available to @@ -87,7 +88,7 @@ public class NetworkStatsFactoryTest extends NetworkStatsBaseTest { mFactory = null; if (mTestProc.exists()) { - IoUtils.deleteContents(mTestProc); + FsUtil.deleteContents(mTestProc); } } diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java index fd374bc9e6..95ab322f2e 100644 --- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java @@ -112,13 +112,12 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.ArrayUtils; import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.internal.util.test.FsUtil; import com.android.server.net.NetworkStatsService.NetworkStatsSettings; import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config; import com.android.testutils.HandlerUtils; import com.android.testutils.TestableNetworkStatsProviderBinder; -import libcore.io.IoUtils; - import org.junit.After; import org.junit.Before; import org.junit.Ignore; @@ -213,7 +212,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { mServiceContext = new MockContext(context); mStatsDir = context.getFilesDir(); if (mStatsDir.exists()) { - IoUtils.deleteContents(mStatsDir); + FsUtil.deleteContents(mStatsDir); } PowerManager powerManager = (PowerManager) mServiceContext.getSystemService( @@ -283,7 +282,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { @After public void tearDown() throws Exception { - IoUtils.deleteContents(mStatsDir); + FsUtil.deleteContents(mStatsDir); mServiceContext = null; mStatsDir = null; From 73e39baccb4c1eb0f96383503140018f3d3babee Mon Sep 17 00:00:00 2001 From: Orion Hodson Date: Fri, 30 Apr 2021 17:48:03 +0100 Subject: [PATCH 210/232] Move to renamed NDK symbol AFileDescriptor_getFd Bug: 185256332 Test: TH (cherry picked from commit a1a2ccb8d7acdacf32d9d9e9f5168481794cc45a) Merged-In: Ic13f1d9832d5ae5b6ae4b96323025d9b695fdaf9 Change-Id: I83ef7acccbea13ec00040ba844054afa42faec41 --- framework/jni/android_net_NetworkUtils.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp index 48e262a6b1..f17baf9f67 100644 --- a/framework/jni/android_net_NetworkUtils.cpp +++ b/framework/jni/android_net_NetworkUtils.cpp @@ -76,7 +76,7 @@ static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, filter_code, }; - int fd = AFileDescriptor_getFD(env, javaFd); + int fd = AFileDescriptor_getFd(env, javaFd); if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { jniThrowExceptionFmt(env, "java/net/SocketException", "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); @@ -86,7 +86,7 @@ static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) { int optval_ignored = 0; - int fd = AFileDescriptor_getFD(env, javaFd); + int fd = AFileDescriptor_getFd(env, javaFd); if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) != 0) { jniThrowExceptionFmt(env, "java/net/SocketException", @@ -112,7 +112,7 @@ static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv * static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd, jint netId) { - return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd)); + return setNetworkForSocket(netId, AFileDescriptor_getFd(env, javaFd)); } static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst) @@ -160,7 +160,7 @@ static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint } static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) { - int fd = AFileDescriptor_getFD(env, javaFd); + int fd = AFileDescriptor_getFd(env, javaFd); int rcode; std::vector buf(MAXPACKETSIZE, 0); @@ -187,7 +187,7 @@ static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, job } static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) { - int fd = AFileDescriptor_getFD(env, javaFd); + int fd = AFileDescriptor_getFd(env, javaFd); resNetworkCancel(fd); jniSetFileDescriptorOfFD(env, javaFd, -1); } @@ -213,7 +213,7 @@ static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, j return NULL; } - int fd = AFileDescriptor_getFD(env, javaFd); + int fd = AFileDescriptor_getFd(env, javaFd); struct tcp_repair_window trw = {}; socklen_t size = sizeof(trw); From b11ba415f6dfe0175c2040367300350c42c201cb Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 23 Apr 2021 15:39:02 +0900 Subject: [PATCH 211/232] Move config_apf* resources to NetworkStack The resources are only read by NetworkStack through their respective ApfCapabilities SystemApi methods. As the resources are being migrated out of frameworks/base resources anyway, move them directly to NetworkStack instead of moving them to ServiceConnectivityResources. Also test that the framework resources are not overlaid or modified. This should avoid OEM integration errors where the overlays are kept as in R, without overlaying the resource in the NetworkStack package. Bug: 185850634 Test: atest CtsNetTestCases BYPASS_INCLUSIVE_LANGUAGE_REASON=Need to mention legacy APIs Change-Id: I7a15ddcad5af11fa307d9dbe3a77b31a1179e5b3 --- .../src/android/net/apf/ApfCapabilities.java | 30 +++-------------- .../res/values/config.xml | 16 --------- .../res/values/overlayable.xml | 2 -- .../android/net/apf/ApfCapabilitiesTest.java | 33 +++++++++++++++++++ 4 files changed, 37 insertions(+), 44 deletions(-) diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java index 85b24713f2..663c1b3d2d 100644 --- a/framework/src/android/net/apf/ApfCapabilities.java +++ b/framework/src/android/net/apf/ApfCapabilities.java @@ -131,43 +131,21 @@ public final class ApfCapabilities implements Parcelable { * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. */ public static boolean getApfDrop8023Frames() { - // TODO(b/183076074): remove reading resources from system resources + // TODO: deprecate/remove this method (now unused in the platform), as the resource was + // moved to NetworkStack. final Resources systemRes = Resources.getSystem(); final int id = systemRes.getIdentifier("config_apfDrop802_3Frames", "bool", "android"); return systemRes.getBoolean(id); } - /** - * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames. - * @hide - */ - public static boolean getApfDrop8023Frames(@NonNull Context context) { - final ConnectivityResources res = getResources(context); - // TODO(b/183076074): use R.bool.config_apfDrop802_3Frames directly - final int id = res.get().getIdentifier("config_apfDrop802_3Frames", "bool", - res.getResourcesContext().getPackageName()); - return res.get().getBoolean(id); - } - /** * @return An array of denylisted EtherType, packets with EtherTypes within it will be dropped. */ public static @NonNull int[] getApfEtherTypeBlackList() { - // TODO(b/183076074): remove reading resources from system resources + // TODO: deprecate/remove this method (now unused in the platform), as the resource was + // moved to NetworkStack. final Resources systemRes = Resources.getSystem(); final int id = systemRes.getIdentifier("config_apfEthTypeBlackList", "array", "android"); return systemRes.getIntArray(id); } - - /** - * @return An array of denylisted EtherType, packets with EtherTypes within it will be dropped. - * @hide - */ - public static @NonNull int[] getApfEtherTypeDenyList(@NonNull Context context) { - final ConnectivityResources res = getResources(context); - // TODO(b/183076074): use R.array.config_apfEthTypeDenyList directly - final int id = res.get().getIdentifier("config_apfEthTypeDenyList", "array", - res.getResourcesContext().getPackageName()); - return res.get().getIntArray(id); - } } diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml index 9ff2a2209e..078a9eb582 100644 --- a/service/ServiceConnectivityResources/res/values/config.xml +++ b/service/ServiceConnectivityResources/res/values/config.xml @@ -52,22 +52,6 @@ 12,60000 - - true - - - - 0x88A2 - 0x88A4 - 0x88B8 - 0x88CD - 0x88E3 - -