Rename groupHint to cluster. am: c6ad18a80d am: 3453e859fe am: 9dc17ef6bc
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/NetworkStack/+/11629113
Change-Id: I5db8a596a049c2ed4c92a075208461047ea147e4
diff --git a/Android.bp b/Android.bp
index ccdaf7a..37e001c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -87,11 +87,13 @@
libs: ["unsupportedappusage"],
static_libs: [
"androidx.annotation_annotation",
- "netd_aidl_interface-V3-java",
+ "netd_aidl_interface-java",
"netlink-client",
"networkstack-client",
+ "net-utils-framework-common",
+ // See note on statsprotos when adding/updating proto build rules
"datastallprotosnano",
- "networkstackprotosnano",
+ "statsprotos",
"captiveportal-lib",
],
plugins: ["java_api_finder"],
@@ -126,7 +128,7 @@
defaults: ["NetworkStackReleaseApiLevel"],
srcs: [
"src/**/*.java",
- ":statslog-networkstack-java-gen-q",
+ ":statslog-networkstack-java-gen",
],
// API stable uses a jarjared version of the shims
static_libs: [
@@ -214,7 +216,10 @@
defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"],
static_libs: ["NetworkStackApiStableLib"],
manifest: "AndroidManifestBase.xml",
- visibility: ["//frameworks/base/tests/net/integration"],
+ visibility: [
+ "//frameworks/base/tests/net/integration",
+ "//cts/tests/tests/net",
+ ],
}
cc_library_shared {
@@ -249,15 +254,6 @@
}
genrule {
- name: "statslog-networkstack-java-gen-q",
- tools: ["stats-log-api-gen"],
- cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
- " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
- " --compileQ",
- out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"],
-}
-
-genrule {
name: "statslog-networkstack-java-gen",
tools: ["stats-log-api-gen"],
cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
@@ -288,3 +284,19 @@
// The permission configuration *must* be included to ensure security of the device
required: ["NetworkPermissionConfig"],
}
+
+// When adding or modifying protos, the jarjar rules and possibly proguard rules need
+// to be updated: proto libraries may pull additional static libraries.
+java_library_static {
+ name: "statsprotos",
+ proto: {
+ type: "lite",
+ },
+ srcs: [
+ "src/com/android/networkstack/metrics/stats.proto",
+ ],
+ static_libs: [
+ "networkstackprotos",
+ ],
+ defaults: ["NetworkStackReleaseApiLevel"],
+}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
index 0c75f27..5639386 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
@@ -93,6 +93,16 @@
}
@Override
+ public long getByteLimit() {
+ return mData.getByteLimit();
+ }
+
+ @Override
+ public long getExpiryTimeMillis() {
+ return mData.getExpiryTimeMillis();
+ }
+
+ @Override
public Uri getUserPortalUrl() {
return mData.getUserPortalUrl();
}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
index eb4fc07..27fd745 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
@@ -37,4 +37,9 @@
DataStallReport.DETECTION_METHOD_DNS_EVENTS;
public static final int DETECTION_METHOD_TCP_METRICS =
DataStallReport.DETECTION_METHOD_TCP_METRICS;
+
+ /**
+ * @see android.net.NetworkCapabilities
+ */
+ public static final int TRANSPORT_TEST = 7;
}
diff --git a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java b/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
index a328720..0184845 100644
--- a/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
+++ b/apishim/31/com/android/networkstack/apishim/ConstantsShim.java
@@ -30,4 +30,8 @@
*/
@VisibleForTesting
public static final int VERSION = 31;
+
+ // When removing this shim, the version in NetworkMonitorUtils should be removed too.
+ // TODO: add TRANSPORT_TEST to system API in API 31 (it is only a test API as of R)
+ public static final int TRANSPORT_TEST = 7;
}
diff --git a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
index fe99c13..a18ba49 100644
--- a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
@@ -30,6 +30,16 @@
boolean isCaptive();
/**
+ * @see android.net.CaptivePortalData#getByteLimit()
+ */
+ long getByteLimit();
+
+ /**
+ * @see android.net.CaptivePortalData#getExpiryTimeMillis()
+ */
+ long getExpiryTimeMillis();
+
+ /**
* @see android.net.CaptivePortalData#getUserPortalUrl()
*/
Uri getUserPortalUrl();
diff --git a/common/moduleutils/Android.bp b/common/moduleutils/Android.bp
index 93ee00c..9dfec42 100644
--- a/common/moduleutils/Android.bp
+++ b/common/moduleutils/Android.bp
@@ -22,7 +22,6 @@
srcs: [
"src/android/net/util/SharedLog.java",
"src/android/net/shared/InitialConfiguration.java",
- "src/android/net/shared/IpConfigurationParcelableUtil.java",
"src/android/net/shared/Layer2Information.java",
"src/android/net/shared/LinkPropertiesParcelableUtil.java",
"src/android/net/shared/ParcelableUtil.java",
diff --git a/common/moduleutils/src/android/net/shared/NetdUtils.java b/common/moduleutils/src/android/net/shared/NetdUtils.java
index 0137bb7..5fa29c9 100644
--- a/common/moduleutils/src/android/net/shared/NetdUtils.java
+++ b/common/moduleutils/src/android/net/shared/NetdUtils.java
@@ -17,6 +17,7 @@
package android.net.shared;
import static android.net.RouteInfo.RTN_UNICAST;
+import static android.system.OsConstants.EBUSY;
import android.net.INetd;
import android.net.IpPrefix;
@@ -24,6 +25,8 @@
import android.net.TetherConfigParcel;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.SystemClock;
+import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -33,6 +36,8 @@
* @hide
*/
public class NetdUtils {
+ private static final String TAG = NetdUtils.class.getSimpleName();
+
/** Start tethering. */
public static void tetherStart(final INetd netd, final boolean usingLegacyDnsProxy,
final String[] dhcpRange) throws RemoteException, ServiceSpecificException {
@@ -45,14 +50,46 @@
/** Setup interface for tethering. */
public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest)
throws RemoteException, ServiceSpecificException {
- netd.tetherInterfaceAdd(iface);
+ tetherInterface(netd, iface, dest, 20 /* maxAttempts */, 50 /* pollingIntervalMs */);
+ }
- netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+ /** Setup interface with configurable retries for tethering. */
+ public static void tetherInterface(final INetd netd, final String iface, final IpPrefix dest,
+ int maxAttempts, int pollingIntervalMs)
+ throws RemoteException, ServiceSpecificException {
+ netd.tetherInterfaceAdd(iface);
+ networkAddInterface(netd, iface, maxAttempts, pollingIntervalMs);
List<RouteInfo> routes = new ArrayList<>();
routes.add(new RouteInfo(dest, null, iface, RTN_UNICAST));
RouteUtils.addRoutesToLocalNetwork(netd, iface, routes);
}
+ /**
+ * Retry Netd#networkAddInterface for EBUSY error code.
+ * If the same interface (e.g., wlan0) is in client mode and then switches to tethered mode.
+ * There can be a race where puts the interface into the local network but interface is still
+ * in use in netd because the ConnectivityService thread hasn't processed the disconnect yet.
+ * See b/158269544 for detail.
+ */
+ private static void networkAddInterface(final INetd netd, final String iface,
+ int maxAttempts, int pollingIntervalMs)
+ throws ServiceSpecificException, RemoteException {
+ for (int i = 1; i <= maxAttempts; i++) {
+ try {
+ netd.networkAddInterface(INetd.LOCAL_NET_ID, iface);
+ return;
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == EBUSY && i < maxAttempts) {
+ SystemClock.sleep(pollingIntervalMs);
+ continue;
+ }
+
+ Log.e(TAG, "Retry Netd#networkAddInterface failure: " + e);
+ throw e;
+ }
+ }
+ }
+
/** Reset interface for tethering. */
public static void untetherInterface(final INetd netd, String iface)
throws RemoteException, ServiceSpecificException {
diff --git a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
index 8983d00..981a576 100644
--- a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
+++ b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
@@ -20,11 +20,21 @@
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_TRUSTED;
+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 android.net.NetworkCapabilities;
/** @hide */
public class NetworkMonitorUtils {
+ // This class is used by both NetworkMonitor and ConnectivityService, so it cannot use
+ // NetworkStack shims, but at the same time cannot use non-system APIs.
+ // TRANSPORT_TEST is test API as of R (so it is enforced to always be 7 and can't be changed),
+ // and it is being added as a system API in S.
+ // TODO: use NetworkCapabilities.TRANSPORT_TEST once NetworkStack builds against API 31.
+ private static final int TRANSPORT_TEST = 7;
// Network conditions broadcast constants
public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
@@ -47,11 +57,33 @@
* @param nc Network capabilities of the network to test.
*/
public static boolean isPrivateDnsValidationRequired(NetworkCapabilities nc) {
+ if (nc == null) return false;
+
// TODO: Consider requiring validation for DUN networks.
- return nc != null
- && nc.hasCapability(NET_CAPABILITY_INTERNET)
+ if (nc.hasCapability(NET_CAPABILITY_INTERNET)
&& nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
- && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+ && nc.hasCapability(NET_CAPABILITY_TRUSTED)) {
+ // Real networks
+ return true;
+ }
+
+ // TODO: once TRANSPORT_TEST is @SystemApi in S and S SDK is stable (so constant shims can
+ // be replaced with the SDK constant that will be inlined), replace isTestNetwork with
+ // hasTransport(TRANSPORT_TEST)
+
+ // Test networks that also have one of the major transport types are attempting to replicate
+ // that transport on a test interface (for example, test ethernet networks with
+ // EthernetManager#setIncludeTestInterfaces). Run validation on them for realistic tests.
+ // See also comments on EthernetManager#setIncludeTestInterfaces and on TestNetworkManager.
+ if (nc.hasTransport(TRANSPORT_TEST) && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) && (
+ nc.hasTransport(TRANSPORT_WIFI)
+ || nc.hasTransport(TRANSPORT_CELLULAR)
+ || nc.hasTransport(TRANSPORT_BLUETOOTH)
+ || nc.hasTransport(TRANSPORT_ETHERNET))) {
+ return true;
+ }
+
+ return false;
}
/**
diff --git a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java b/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
index cb613fb..5f5699c 100644
--- a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
+++ b/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
@@ -68,12 +68,12 @@
// TODO: Delete this default timeout once those callers that care are
// fixed to pass in their preferred timeout.
//
- // We pick 36 seconds so we can send DHCP requests at
+ // We pick 18 seconds so we can send DHCP requests at
//
- // t=0, t=2, t=6, t=14, t=30
+ // t=0, t=1, t=3, t=7, t=16
//
// allowing for 10% jitter.
- private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+ private static final int DEFAULT_TIMEOUT_MS = 18 * 1000;
/**
* Builder to create a {@link ProvisioningConfiguration}.
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 43d482f..b5a3e95 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -44,6 +44,7 @@
"4",
"5",
"6",
+ "7",
],
visibility: [
"//system/tools/aidl/build",
@@ -123,6 +124,7 @@
"src/android/net/IpMemoryStoreClient.java",
"src/android/net/ipmemorystore/**/*.java",
"src/android/net/networkstack/**/*.java",
+ "src/android/net/shared/**/*.java",
],
static_libs: [
"ipmemorystore-aidl-interfaces-java",
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/.hash b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/.hash
new file mode 100644
index 0000000..786a6f7
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/.hash
@@ -0,0 +1 @@
+31826566143ef882d67fac9f24566f73df4907b4
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..bf7a26d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,30 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface IIpMemoryStore {
+ oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
+ oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
+ oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
+ oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
+ oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
+ oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
+ oneway void factoryReset();
+ oneway void delete(String l2Key, boolean needWipe, android.net.ipmemorystore.IOnStatusAndCountListener listener);
+ oneway void deleteCluster(String cluster, boolean needWipe, android.net.ipmemorystore.IOnStatusAndCountListener listener);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStoreCallbacks.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStoreCallbacks.aidl
new file mode 100644
index 0000000..2024391
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/IIpMemoryStoreCallbacks.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface IIpMemoryStoreCallbacks {
+ oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/Blob.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..8a1b57e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+parcelable Blob {
+ byte[] data;
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..e711272
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+interface IOnBlobRetrievedListener {
+ oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..4abecb9
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+interface IOnL2KeyResponseListener {
+ oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
new file mode 100644
index 0000000..05c48b3
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+interface IOnNetworkAttributesRetrievedListener {
+ oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
new file mode 100644
index 0000000..0bc8c5e
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+interface IOnSameL3NetworkResponseListener {
+ oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusAndCountListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
new file mode 100644
index 0000000..cf30fa1
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+interface IOnStatusAndCountListener {
+ oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status, int count);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..e71de47
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+interface IOnStatusListener {
+ oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..92a570d
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+parcelable NetworkAttributesParcelable {
+ byte[] assignedV4Address;
+ long assignedV4AddressExpiry;
+ String cluster;
+ android.net.ipmemorystore.Blob[] dnsAddresses;
+ int mtu;
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..eca0987
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+parcelable SameL3NetworkResponseParcelable {
+ String l2Key1;
+ String l2Key2;
+ float confidence;
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/StatusParcelable.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..7554608
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/7/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+parcelable StatusParcelable {
+ int resultCode;
+}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
index ac87e59..bf7a26d 100644
--- a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/IIpMemoryStore.aidl
@@ -25,4 +25,6 @@
oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
oneway void factoryReset();
+ oneway void delete(String l2Key, boolean needWipe, android.net.ipmemorystore.IOnStatusAndCountListener listener);
+ oneway void deleteCluster(String cluster, boolean needWipe, android.net.ipmemorystore.IOnStatusAndCountListener listener);
}
diff --git a/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
new file mode 100644
index 0000000..cf30fa1
--- /dev/null
+++ b/common/networkstackclient/aidl_api/ipmemorystore-aidl-interfaces/current/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.ipmemorystore;
+/* @hide */
+interface IOnStatusAndCountListener {
+ oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status, int count);
+}
diff --git a/common/networkstackclient/src/android/net/IIpMemoryStore.aidl b/common/networkstackclient/src/android/net/IIpMemoryStore.aidl
index add221a..3bb58bf 100644
--- a/common/networkstackclient/src/android/net/IIpMemoryStore.aidl
+++ b/common/networkstackclient/src/android/net/IIpMemoryStore.aidl
@@ -22,6 +22,7 @@
import android.net.ipmemorystore.IOnL2KeyResponseListener;
import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusAndCountListener;
import android.net.ipmemorystore.IOnStatusListener;
/** {@hide} */
@@ -39,8 +40,7 @@
* @param attributes The attributes for this network.
* @param listener A listener that will be invoked to inform of the completion of this call,
* or null if the client is not interested in learning about success/failure.
- * @return (through the listener) The L2 key. This is useful if the L2 key was not specified.
- * If the call failed, the L2 key will be null.
+ * @return (through the listener) A status to indicate success or failure.
*/
void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes,
IOnStatusListener listener);
@@ -115,4 +115,41 @@
* Delete all data because a factory reset operation is in progress.
*/
void factoryReset();
+
+ /**
+ * Delete a single entry.
+ *
+ * @param l2key The L2 key of the entry to delete.
+ * @param needWipe Whether the data must be wiped from disk immediately. This makes the
+ * operation vastly more expensive as the database files will have to be copied
+ * and created again from the old files (see sqlite3 VACUUM operation for
+ * details) and makes no functional difference; only pass true if security or
+ * privacy demands this data must be removed from disk immediately.
+ * Note that this can fail for storage reasons. The passed listener will then
+ * receive an appropriate error status with the number of deleted rows.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * @return (through the listener) A status to indicate success and the number of deleted records
+ */
+ void delete(String l2Key, boolean needWipe, IOnStatusAndCountListener listener);
+
+ /**
+ * Delete all entries in a cluster.
+ *
+ * This method will delete all entries in the memory store that have the cluster attribute
+ * passed as an argument.
+ *
+ * @param cluster The cluster to delete.
+ * @param needWipe Whether the data must be wiped from disk immediately. This makes the
+ * operation vastly more expensive as the database files will have to be copied
+ * and created again from the old files (see sqlite3 VACUUM operation for
+ * details) and makes no functional difference; only pass true if security or
+ * privacy demands this data must be removed from disk immediately.
+ * Note that this can fail for storage reasons. The passed listener will then
+ * receive an appropriate error status with the number of deleted rows.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * @return (through the listener) A status to indicate success and the number of deleted records
+ */
+ void deleteCluster(String cluster, boolean needWipe, IOnStatusAndCountListener listener);
}
diff --git a/common/networkstackclient/src/android/net/IpMemoryStoreClient.java b/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
index 014b528..a1c5694 100644
--- a/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
+++ b/common/networkstackclient/src/android/net/IpMemoryStoreClient.java
@@ -22,6 +22,7 @@
import android.net.ipmemorystore.Blob;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.OnBlobRetrievedListener;
+import android.net.ipmemorystore.OnDeleteStatusListener;
import android.net.ipmemorystore.OnL2KeyResponseListener;
import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
import android.net.ipmemorystore.OnSameL3NetworkResponseListener;
@@ -91,6 +92,7 @@
() -> service.storeNetworkAttributes(l2Key, attributes.toParcelable(),
OnStatusListener.toAIDL(listener))));
} catch (ExecutionException m) {
+ if (null == listener) return;
ignoringRemoteException("Error storing network attributes",
() -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
}
@@ -115,6 +117,7 @@
() -> service.storeBlob(l2Key, clientId, name, data,
OnStatusListener.toAIDL(listener))));
} catch (ExecutionException m) {
+ if (null == listener) return;
ignoringRemoteException("Error storing blob",
() -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
}
@@ -142,7 +145,8 @@
OnL2KeyResponseListener.toAIDL(listener))));
} catch (ExecutionException m) {
ignoringRemoteException("Error finding L2 Key",
- () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null));
+ () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN),
+ null /* l2Key */));
}
}
@@ -163,7 +167,8 @@
OnSameL3NetworkResponseListener.toAIDL(listener))));
} catch (ExecutionException m) {
ignoringRemoteException("Error checking for network sameness",
- () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null));
+ () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN),
+ null /* response */));
}
}
@@ -185,7 +190,7 @@
} catch (ExecutionException m) {
ignoringRemoteException("Error retrieving network attributes",
() -> listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN),
- null, null));
+ null /* l2Key */, null /* attributes */));
}
}
@@ -209,7 +214,67 @@
} catch (ExecutionException m) {
ignoringRemoteException("Error retrieving blob",
() -> listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN),
- null, null, null));
+ null /* l2Key */, null /* name */, null /* blob */));
+ }
+ }
+
+ /**
+ * Delete a single entry.
+ *
+ * @param l2Key The L2 key of the entry to delete.
+ * @param needWipe Whether the data must be wiped from disk immediately. This makes the
+ * operation vastly more expensive as the database files will have to be copied
+ * and created again from the old files (see sqlite3 VACUUM operation for
+ * details) and makes no functional difference; only pass true if security or
+ * privacy demands this data must be removed from disk immediately.
+ * Note that this can fail for storage reasons. The passed listener will then
+ * receive an appropriate error status with the number of deleted rows.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * returns (through the listener) A status to indicate success and the number of deleted records
+ */
+ public void delete(@NonNull final String l2Key, final boolean needWipe,
+ @Nullable final OnDeleteStatusListener listener) {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(() ->
+ service.delete(l2Key, needWipe, OnDeleteStatusListener.toAIDL(listener))));
+ } catch (ExecutionException m) {
+ if (null == listener) return;
+ ignoringRemoteException("Error deleting from the memory store",
+ () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN),
+ 0 /* deletedRecords */));
+ }
+ }
+
+ /**
+ * Delete all entries in a cluster.
+ *
+ * This method will delete all entries in the memory store that have the cluster attribute
+ * passed as an argument.
+ *
+ * @param cluster The cluster to delete.
+ * @param needWipe Whether the data must be wiped from disk immediately. This makes the
+ * operation vastly more expensive as the database files will have to be copied
+ * and created again from the old files (see sqlite3 VACUUM operation for
+ * details) and makes no functional difference; only pass true if security or
+ * privacy demands this data must be removed from disk immediately.
+ * Note that this can fail for storage reasons. The passed listener will then
+ * receive an appropriate error status with the number of deleted rows.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * returns (through the listener) A status to indicate success and the number of deleted records
+ */
+ public void deleteCluster(@NonNull final String cluster, final boolean needWipe,
+ @Nullable final OnDeleteStatusListener listener) {
+ try {
+ runWhenServiceReady(service -> ignoringRemoteException(
+ () -> service.deleteCluster(cluster, needWipe,
+ OnDeleteStatusListener.toAIDL(listener))));
+ } catch (ExecutionException m) {
+ if (null == listener) return;
+ ignoringRemoteException("Error deleting from the memory store",
+ () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN),
+ 0 /* deletedRecords */));
}
}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusAndCountListener.aidl b/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
new file mode 100644
index 0000000..c19b5c1
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/IOnStatusAndCountListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnStatusAndCountListener {
+ /**
+ * The operation has completed with the specified status, and supplied the passed count
+ * as call-specific additional data.
+ */
+ void onComplete(in StatusParcelable status, int count);
+}
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java b/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java
index 104ac79..2e444fe 100644
--- a/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java
+++ b/common/networkstackclient/src/android/net/ipmemorystore/NetworkAttributes.java
@@ -218,6 +218,22 @@
private Integer mMtu;
/**
+ * Constructs a new Builder.
+ */
+ public Builder() {}
+
+ /**
+ * Constructs a Builder from the passed NetworkAttributes.
+ */
+ public Builder(@NonNull final NetworkAttributes attributes) {
+ mAssignedAddress = attributes.assignedV4Address;
+ mAssignedAddressExpiry = attributes.assignedV4AddressExpiry;
+ mCluster = attributes.cluster;
+ mDnsAddresses = new ArrayList<>(attributes.dnsAddresses);
+ mMtu = attributes.mtu;
+ }
+
+ /**
* Set the assigned address.
* @param assignedV4Address The assigned address.
* @return This builder.
diff --git a/common/networkstackclient/src/android/net/ipmemorystore/OnDeleteStatusListener.java b/common/networkstackclient/src/android/net/ipmemorystore/OnDeleteStatusListener.java
new file mode 100644
index 0000000..7138877
--- /dev/null
+++ b/common/networkstackclient/src/android/net/ipmemorystore/OnDeleteStatusListener.java
@@ -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.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A listener for the IpMemoryStore to return a status to a client.
+ * @hide
+ */
+public interface OnDeleteStatusListener {
+ /**
+ * The operation has completed with the specified status, and deleted the specified
+ * number of records. The operation can fail with a non-zero count of deleted rows as
+ * wipe requests may fail for lack of storage. See the documentation of each deletion
+ * method for details.
+ */
+ void onComplete(Status status, int deletedRecords);
+
+ /** Converts this OnDeleteStatusListener to a parcelable object */
+ @NonNull
+ static IOnStatusAndCountListener toAIDL(@Nullable final OnDeleteStatusListener listener) {
+ return new IOnStatusAndCountListener.Stub() {
+ @Override
+ public void onComplete(final StatusParcelable statusParcelable, int deletedRecords) {
+ if (null != listener) {
+ listener.onComplete(new Status(statusParcelable), deletedRecords);
+ }
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+ };
+ }
+}
diff --git a/common/networkstackclient/src/android/net/shared/IpConfigurationParcelableUtil.java b/common/networkstackclient/src/android/net/shared/IpConfigurationParcelableUtil.java
new file mode 100644
index 0000000..e8152cf
--- /dev/null
+++ b/common/networkstackclient/src/android/net/shared/IpConfigurationParcelableUtil.java
@@ -0,0 +1,48 @@
+/*
+ * 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.shared;
+
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+
+import java.net.InetAddress;
+
+/**
+ * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient
+ * configuration classes.
+ * @hide
+ */
+public final class IpConfigurationParcelableUtil {
+
+ /**
+ * Convert InetAddress to String.
+ * TODO: have an InetAddressParcelable
+ */
+ public static String parcelAddress(@Nullable InetAddress addr) {
+ if (addr == null) return null;
+ return addr.getHostAddress();
+ }
+
+ /**
+ * Convert String to InetAddress.
+ * TODO: have an InetAddressParcelable
+ */
+ public static InetAddress unparcelAddress(@Nullable String addr) {
+ if (addr == null) return null;
+ return InetAddresses.parseNumericAddress(addr);
+ }
+}
diff --git a/jarjar-rules-shared.txt b/jarjar-rules-shared.txt
index 7346b1a..048c976 100644
--- a/jarjar-rules-shared.txt
+++ b/jarjar-rules-shared.txt
@@ -1,10 +1,14 @@
-rule com.android.internal.util.** android.net.networkstack.util.@1
+# Classes from net-utils-framework-common
+rule com.android.net.module.util.** com.android.networkstack.util.@1
-rule android.net.shared.Inet4AddressUtils* android.net.networkstack.shared.Inet4AddressUtils@1
-rule android.net.shared.InetAddressUtils* android.net.networkstack.shared.InetAddressUtils@1
+rule com.android.internal.util.** android.net.networkstack.util.@1
+rule com.google.protobuf.** com.android.networkstack.protobuf.@1
+
+# Classes from net-utils-framework-common
+rule com.android.net.module.util.** com.android.networkstack.util.@1
# Ignore DhcpResultsParcelable, but jarjar DhcpResults
# TODO: move DhcpResults into services.net and delete from here
rule android.net.DhcpResultsParcelable* @0
rule android.net.DhcpResults* android.net.networkstack.DhcpResults@1
-rule android.net.LocalLog* android.net.networkstack.LocalLog@1
+rule android.util.LocalLog* android.net.networkstack.util.LocalLog@1
diff --git a/proguard.flags b/proguard.flags
index c60f6c3..af4262a 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -7,3 +7,10 @@
static final int CMD_*;
static final int EVENT_*;
}
+
+# The lite proto runtime uses reflection to access fields based on the names in
+# the schema, keep all the fields.
+# This replicates the base proguard rule used by the build by default
+# (proguard_basic_keeps.flags), but needs to be specified here because the
+# com.google.protobuf package is jarjared to the below package.
+-keepclassmembers class * extends com.android.networkstack.protobuf.MessageLite { <fields>; }
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 0000000..7edeae2
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Kontroleringsportaalstawing"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Kennisgewings word gewys wanneer die toestel suksesvol vir \'n kontroleringsportaalnetwerk gestaaf het"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Netwerkplekinligting"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Kennisgewings word gewys om aan te dui dat die netwerk \'n plekinligtingbladsy het"</string>
+ <string name="connected" msgid="4563643884927480998">"Gekoppel"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Is gekoppel / Tik om webwerf te bekyk"</string>
+ <string name="application_label" msgid="1322847171305285454">"Netwerkbestuurder"</string>
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 0000000..a5c349e
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"የተያዥ መግቢያ ማረጋገጥ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"መሣሪያው በተሳካ ሁኔታ ከአንድ ተያዥ መግቢያ አውታረ መረብ ጋር ሲረጋገጥ ማሳወቂያዎች ይታያሉ"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"የአውታረ መረብ መድረክ መረጃ"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"አውታረ መረቡ የመድረክ መረጃ ግገጽ እንዳለው ለማመልከት ማሳወቂያዎች ይታያሉ"</string>
+ <string name="connected" msgid="4563643884927480998">"ተገናኝቷል"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"ተገናኝቷል / የድር ጣቢያን ለማየት መታ ያድርጉ"</string>
+ <string name="application_label" msgid="1322847171305285454">"የአውታረ መረብ አስተዳዳሪ"</string>
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 0000000..fe60a49
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"مصادقة مدخل مشروط الوصول إليه"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"يتم عرض الإشعارات عندما تتم مصادقة الجهاز بنجاح مع شبكة مدخل مشروط الوصول إليه."</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"معلومات عن المكان الذي يوفّر الشبكة"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"الإشعارات التي يتم عرضها لتشير إلى أن الشبكة تحتوي على صفحة معلومات عن المكان."</string>
+ <string name="connected" msgid="4563643884927480998">"الجهاز متصل الآن بالإنترنت."</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"متصل بالشبكة، انقر للانتقال إلى الموقع الإلكتروني"</string>
+ <string name="application_label" msgid="1322847171305285454">"إدارة الشبكة"</string>
+</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
new file mode 100644
index 0000000..2a51122
--- /dev/null
+++ b/res/values-as/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"কেপটিভ প’ৰ্টেলৰ প্ৰমাণীকৰণ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"ডিভাইচটোৱে কোনো কেপটিভ প’ৰ্টেল নেটৱর্কত সফলতাৰে বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কৰিলে জাননীসমূহ দেখুওৱা হয়"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"নেটৱৰ্ক স্থলীৰ তথ্য"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"নেটৱৰ্কৰ এখন স্থলীৰ তথ্য পৃষ্ঠা থকাটো দৰ্শাবলৈ জাননীসমূহ দেখুওৱা হয়"</string>
+ <string name="connected" msgid="4563643884927480998">"সংযোগ কৰা হৈছে"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"সংযুক্ত / ৱেবছাইট চাবলৈ টিপক"</string>
+ <string name="application_label" msgid="1322847171305285454">"নেটৱৰ্ক মেনেজাৰ"</string>
+</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
new file mode 100644
index 0000000..0dfd623
--- /dev/null
+++ b/res/values-az/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Avtorizasiya tələb edən portalda doğrulama"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Cihaz daxili portal şəbəkəsində uğurla doğrulandıqda bildirişlər göstərilir"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Şəbəkənin məkan məlumatı"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Şəbəkədə məkan məlumatı səhifəsi olduğunu göstərən bildirişlər"</string>
+ <string name="connected" msgid="4563643884927480998">"Qoşulub"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Qoşulub / Veb sayta baxmaq üçün toxunun"</string>
+ <string name="application_label" msgid="1322847171305285454">"Şəbəkə meneceri"</string>
+</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..e53b2c9
--- /dev/null
+++ b/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Potvrda identiteta na ulaznom portalu"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Obaveštenja koja se prikazuju kada se potvrdi identitet uređaja na mreži ulaznog portala"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informacije o mestu na mreži"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Obaveštenja koja se prikazuju da bi pokazala da mreža ima stranicu sa informacijama o mestu"</string>
+ <string name="connected" msgid="4563643884927480998">"Povezano"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Povezano/dodirnite da biste pregledali veb-sajt"</string>
+ <string name="application_label" msgid="1322847171305285454">"Menadžer mreže"</string>
+</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
new file mode 100644
index 0000000..dc0d108
--- /dev/null
+++ b/res/values-be/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Аўтэнтыфікацыя на старонцы аўтарызацыі"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Апавяшчэнні, якія паказваюцца, калі прылада паспяхова прайшла аўтэнтыфікацыю на вэб-старонцы аўтарызацыі ў сетцы"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Інфармацыя пра месцазнаходжанне сеткі"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Апавяшчэнні, якія паказваюцца, калі ў сетцы ёсць старонка з інфармацыяй пра яе месцазнаходжанне"</string>
+ <string name="connected" msgid="4563643884927480998">"Падключана"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Падключана / Націсніце для прагляду вэб-сайта"</string>
+ <string name="application_label" msgid="1322847171305285454">"Менеджар сетак"</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 0000000..28118b5
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Удостоверяване в портал за целта"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Известия, които се показват, когато устройството е успешно удостоверено в мрежа с портал за удостоверяване"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Информация за мястото на мрежата"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Известия, показвани, за да укажат, че има страница с информация за мястото, където е реализирана мрежата"</string>
+ <string name="connected" msgid="4563643884927480998">"Установена е връзка"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Установена е връзка/докоснете за преглед на сайта"</string>
+ <string name="application_label" msgid="1322847171305285454">"Мрежови мениджър"</string>
+</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
new file mode 100644
index 0000000..917f2a9
--- /dev/null
+++ b/res/values-bn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"ক্যাপটিভ পোর্টালের যাচাইকরণ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"ক্যাপটিভ পোর্টাল নেটওয়ার্কে ডিভাইস সফলভাবে যাচাই করা হলে এই বিজ্ঞপ্তি দেখানো হয়"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"নেটওয়ার্ক ভেন্যু সংক্রান্ত তথ্য"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"নেটওয়ার্কে একটি ভেন্যুর তথ্য সংক্রান্ত পৃষ্ঠা রয়েছে তা বোঝানোর জন্য বিজ্ঞপ্তি দেখানো হয়েছে"</string>
+ <string name="connected" msgid="4563643884927480998">"কানেক্ট করা হয়েছে"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"কানেক্ট করা আছে / ওয়েবসাইট দেখতে ট্যাপ করুন"</string>
+ <string name="application_label" msgid="1322847171305285454">"নেটওয়ার্ক ম্যানেজার"</string>
+</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
new file mode 100644
index 0000000..ed85f6a
--- /dev/null
+++ b/res/values-bs/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentifikacija na zaštitnom portalu"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Obavještenja koja se prikazuju kada se uređaj uspješno autentificira na mreži sa zaštitnim portalom"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Mrežne informacije o mjestu događaja"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Obavještenja koja se prikazuju da pokažu da mreža ima stranicu s informacijama o mjestu događaja"</string>
+ <string name="connected" msgid="4563643884927480998">"Povezano"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Povezano/Dodirnite da vidite web lokaciju"</string>
+ <string name="application_label" msgid="1322847171305285454">"Upravitelj mreže"</string>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 0000000..4e8d3ec
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticació del portal captiu"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Les notificacions es mostren quan el dispositiu s\'ha autenticat correctament a una xarxa de portal captiu"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informació sobre el lloc de la xarxa"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Les notificacions es mostren per indicar que la xarxa té una pàgina d\'informació del lloc"</string>
+ <string name="connected" msgid="4563643884927480998">"Connectat"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Connectat / Toca per veure el lloc web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Administrador de xarxes"</string>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..a55fe72
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Ověření captive portálu"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Oznámení zobrazovaná po úspěšném ověření zařízení v síti s captive portálem"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Síť s informacemi o místě"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Oznámení zobrazovaná na znamení, že síť obsahuje stránku s informacemi o místě"</string>
+ <string name="connected" msgid="4563643884927480998">"Připojeno"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Připojeno / Klepnutím zobrazíte web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Správce sítí"</string>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 0000000..e54f11c
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Godkendelse til loginportal"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"De notifikationer, der vises, når enheden er blevet godkendt til et netværk via en loginportal"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Oplysninger om netværksplacering"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifikationer, der vises for at indikere, at netværket har en side med oplysninger om placeringen"</string>
+ <string name="connected" msgid="4563643884927480998">"Der er oprettet forbindelse"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Der er oprettet forbindelse/tryk for at se website"</string>
+ <string name="application_label" msgid="1322847171305285454">"Netværksadministrator"</string>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..502a3b1
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive Portal-Authentifizierung"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Die Benachrichtigungen werden angezeigt, wenn das Gerät bei einem Captive Portal-Netzwerk authentifiziert wurde"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informationen zum Netzwerkstandort"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Die Benachrichtigungen werden angezeigt, um auf ein Netzwerk mit einer Seite hinzuweisen, die weitere Informationen zum Netzwerkstandort enthält"</string>
+ <string name="connected" msgid="4563643884927480998">"Verbunden"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Verbunden/Zum Ansehen der Website tippen"</string>
+ <string name="application_label" msgid="1322847171305285454">"Netzwerkmanager"</string>
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 0000000..32d423b
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Έλεγχος ταυτότητας πύλης υποδοχής"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Εμφανίζονται ειδοποιήσεις όταν η συσκευή έχει πραγματοποιήσει επιτυχώς έλεγχο ταυτότητας σε δίκτυο με πύλη υποδοχής."</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Πληροφορίες τόπου δικτύου"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Εμφανίζονται ειδοποιήσεις για να υποδείξουν ότι το δίκτυο διαθέτει σελίδα πληροφοριών για τον τόπο."</string>
+ <string name="connected" msgid="4563643884927480998">"Συνδέθηκε"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Συνδέθηκε/Πατήστε για προβολή του ιστοτόπου"</string>
+ <string name="application_label" msgid="1322847171305285454">"Διαχειριστής δικτύου"</string>
+</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..d9f3843
--- /dev/null
+++ b/res/values-en-rAU/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string>
+ <string name="connected" msgid="4563643884927480998">"Connected"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string>
+ <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..d9f3843
--- /dev/null
+++ b/res/values-en-rCA/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string>
+ <string name="connected" msgid="4563643884927480998">"Connected"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string>
+ <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..d9f3843
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string>
+ <string name="connected" msgid="4563643884927480998">"Connected"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string>
+ <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..d9f3843
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string>
+ <string name="connected" msgid="4563643884927480998">"Connected"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string>
+ <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..c6a7270
--- /dev/null
+++ b/res/values-en-rXC/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate the network has a venue information page"</string>
+ <string name="connected" msgid="4563643884927480998">"Connected"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Connected / Tap to view website"</string>
+ <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..915dfc5
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticación de portal cautivo"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificaciones que se muestran cuando el dispositivo se autenticó correctamente en una red de portal cautivo"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Información del lugar de la red"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificaciones que se muestran para indicar que la red tiene una página de información sobre el lugar"</string>
+ <string name="connected" msgid="4563643884927480998">"Se estableció conexión"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Conectado/presiona para ver el sitio web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Administrador de redes"</string>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..b99fab8
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticación en el portal cautivo"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificaciones que se muestran cuando el dispositivo se autentica correctamente en la red de un portal cautivo"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Información sobre el lugar de la red"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificaciones que se muestran para indicar que la red tiene una página de información sobre el lugar"</string>
+ <string name="connected" msgid="4563643884927480998">"Conectado"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Conectado: toca para ver el sitio web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Administrador de redes"</string>
+</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
new file mode 100644
index 0000000..2ffaff8
--- /dev/null
+++ b/res/values-et/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Hõiveportaali autentimine"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Märguanded, mis kuvatakse siis, kui seadmel õnnestub hõiveportaaliga autentida"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Võrgu asukoha teave"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Märguanded, mis kuvatakse juhul, kui võrgul on asukoha teabeleht"</string>
+ <string name="connected" msgid="4563643884927480998">"Ühendatud"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Ühendatud / puudutage veebisaidi vaatamiseks"</string>
+ <string name="application_label" msgid="1322847171305285454">"Võrguhaldur"</string>
+</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
new file mode 100644
index 0000000..281a68b
--- /dev/null
+++ b/res/values-eu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Sare-zerbitzuen atariko autentifikazioa"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Gailua sare-zerbitzuaren atari batean behar bezala autentifikatzen denean agertuko dira jakinarazpenak"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Sareari dagokion tokiaren informazioa"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Jakinarazpenak agertuko dira sareari dagokion tokiak informazio-orri bat duela adierazteko"</string>
+ <string name="connected" msgid="4563643884927480998">"Konektatuta"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Konektatuta / Sakatu webgunea ikusteko"</string>
+ <string name="application_label" msgid="1322847171305285454">"Sare-kudeatzailea"</string>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 0000000..b595a9e
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"راستیآزمایی درگاه مهمان"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"اعلانهایی که پس از راستیآزمایی موفق دستگاه در یک شبکه درگاه مهمان نشان داده میشوند"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"اطلاعات محل شبکه"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"اعلانهایی که نمایش داده میشوند تا نشان دهند شبکه صفحه اطلاعات محل دارد"</string>
+ <string name="connected" msgid="4563643884927480998">"متصل"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"متصل شد / برای مشاهده وبسایت ضربه بزنید"</string>
+ <string name="application_label" msgid="1322847171305285454">"مدیر شبکه"</string>
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 0000000..975e7d2
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal ‑todennus"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Ilmoitukset, jotka näkyvät, kun laite on todennettu ja yhteydessä captive portal ‑verkkoon"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Verkon paikkatiedot"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Ilmoitukset, jotka kertovat, että verkolla on paikkatietoja sisältävä sivu"</string>
+ <string name="connected" msgid="4563643884927480998">"Yhdistetty"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Yhdistetty / Näytä sivusto napauttamalla"</string>
+ <string name="application_label" msgid="1322847171305285454">"Verkkojen hallinta"</string>
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..5bd6d20
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Authentification du portail captif"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications affichées lorsque l\'appareil s\'est authentifié auprès d\'un réseau doté d\'un portail captif"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Information sur le lieu du réseau"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications indiquant que le réseau dispose d\'une page d\'information à propos du lieu"</string>
+ <string name="connected" msgid="4563643884927480998">"Connecté"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Connecté : touchez pour afficher le site Web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Gestionnaire de réseaux"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..20b737e
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Authentification sur le portail captif"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications affichées lorsque l\'appareil a bien été connecté et authentifié sur un réseau avec portail captif"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informations sur un lieu lié au réseau"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications affichées pour indiquer que le réseau comporte une page d\'informations sur le lieu auquel il est associé"</string>
+ <string name="connected" msgid="4563643884927480998">"Connecté"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Connecté/Appuyer pour afficher le site Web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Gestionnaire de réseaux"</string>
+</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
new file mode 100644
index 0000000..6c6db88
--- /dev/null
+++ b/res/values-gl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticación de portal cativo"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Móstranse notificacións cando o dispositivo se autentica correctamente nunha rede de portal cativo."</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Rede con información sobre o lugar"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Móstranse notificacións para indicar que a rede ten unha páxina de información sobre o lugar"</string>
+ <string name="connected" msgid="4563643884927480998">"Dispositivo conectado á rede"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Conectado. Toca para ver o sitio web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Xestor de redes"</string>
+</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
new file mode 100644
index 0000000..78ebb30
--- /dev/null
+++ b/res/values-gu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"કૅપ્ટિવ પોર્ટલ માટેનું પ્રમાણીકરણ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"જ્યારે કૅપ્ટિવ પોર્ટલ નેટવર્ક પર ડિવાઇસનું સફળતાપૂર્વક પ્રમાણીકરણ થઈ જાય, ત્યારે બતાવવામાં આવતા નોટિફિકેશન"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"નેટવર્કના સ્થળની માહિતી"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"નેટવર્ક સ્થળની માહિતીનું પેજ ધરાવે છે તે સૂચવવા માટે બતાવવામાં આવતા નોટિફિકેશન"</string>
+ <string name="connected" msgid="4563643884927480998">"કનેક્ટ કર્યું છે"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"કનેક્ટેડ / વેબસાઇટ જોવા માટે ટૅપ કરો"</string>
+ <string name="application_label" msgid="1322847171305285454">"નેટવર્ક મેનેજર"</string>
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 0000000..730bb2a
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"कैप्टिव पोर्टल की मदद से पुष्टि करना"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"डिवाइस के कैप्टिव पोर्टल नेटवर्क से पुष्टि होने के बाद सूचनाएं दिखती हैं"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"नेटवर्क पर उस जगह के बारे में जानकारी"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"किसी नेटवर्क पर उस जगह के बारे में बताने वाले पेज की जानकारी दिखाने वाली सूचनाएं"</string>
+ <string name="connected" msgid="4563643884927480998">"नेटवर्क से कनेक्ट हो गया है"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"कनेक्ट है / वेबसाइट देखने के लिए टैप करें"</string>
+ <string name="application_label" msgid="1322847171305285454">"नेटवर्क मैनेजर"</string>
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 0000000..eaf0b2e
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentifikacija na zaštitnom portalu"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Obavijesti koje se prikazuju kada uređaj uspješno prođe provjeru autentičnosti na mreži zaštitnog portala"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Podaci o mrežnom mjestu"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Obavijesti koje se prikazuju kada mreža ima stranicu s podacima o mjestu događaja"</string>
+ <string name="connected" msgid="4563643884927480998">"Povezano"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Povezano/dodirnite da bi se prikazala web-lokacija"</string>
+ <string name="application_label" msgid="1322847171305285454">"Upravitelj mreža"</string>
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 0000000..801961c
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Bejelentkezés hitelesítési portálon"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Értesítés jelenik meg, ha az eszköz sikeresen bejelentkezett egy hitelesítési portált használó hálózaton"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Információk a hálózat helyéről"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Értesítés jelenik meg, amely jelzi, hogy a hálózat a helyszínről szóló információs oldalt is tartalmaz"</string>
+ <string name="connected" msgid="4563643884927480998">"Csatlakozva"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Csatlakozva / Koppintson a webhely megtekintéséhez"</string>
+ <string name="application_label" msgid="1322847171305285454">"Hálózatkezelő"</string>
+</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
new file mode 100644
index 0000000..df1ba31
--- /dev/null
+++ b/res/values-hy/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Նույնականացում մուտքի էջում"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Ծանուցումներ, որոնք ցուցադրվում են, երբ ցանցի մուտքի էջում հաջողվում է նույնականացնել սարքը"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Տվյալներ ցանցի հասանելիության տեղի մասին"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Ծանուցումներ, որոնք ցույց են տալիս այն էջը, որում առկա են տեղեկություններ սարքի հասանելիության վայրի մասին"</string>
+ <string name="connected" msgid="4563643884927480998">"Միացված է"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Միացված է / Հպեք՝ կայքը դիտելու համար"</string>
+ <string name="application_label" msgid="1322847171305285454">"Ցանցի կառավարիչ"</string>
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 0000000..195391c
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentikasi captive portal"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifikasi ditampilkan saat perangkat telah berhasil diautentikasi ke jaringan captive portal"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informasi tempat jaringan"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifikasi ditampilkan untuk menandakan bahwa jaringan memiliki halaman informasi tempat"</string>
+ <string name="connected" msgid="4563643884927480998">"Terhubung"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Terhubung / Ketuk untuk melihat situs"</string>
+ <string name="application_label" msgid="1322847171305285454">"Pengelola jaringan"</string>
+</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
new file mode 100644
index 0000000..8dcaeb7
--- /dev/null
+++ b/res/values-is/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Auðkenning á innskráningarsíðu"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Tilkynningar eru sýndar þegar tæki hefur fengið auðkenningu á neti með innskráningarsíðu"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Upplýsingar nets um vettvang"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Tilkynningar eru sýndar til að gefa til kynna að net innihaldi upplýsingasíðu um vettvang"</string>
+ <string name="connected" msgid="4563643884927480998">"Tengt"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Tengt / Ýttu til að skoða vefsvæði"</string>
+ <string name="application_label" msgid="1322847171305285454">"Netstjórnun"</string>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..7e0fc28
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticazione a captive portal"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifiche mostrate in seguito all\'autenticazione del dispositivo su una rete captive portal"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Info sull\'attività che offre la rete"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifiche mostrate per indicare che la rete ha una pagina di informazioni su un luogo"</string>
+ <string name="connected" msgid="4563643884927480998">"Connesso"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Rete collegata/Tocca per visualizzare il sito web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 0000000..4772691
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"אימות של פורטל שבוי"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"התראות המוצגות כשהמכשיר אומת בהצלחה וחובר לרשת של פורטל שבוי"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"מידע על מקום רשת"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"התראות המוצגות כדי לציין שלרשת יש דף מידע על מקום"</string>
+ <string name="connected" msgid="4563643884927480998">"מחובר"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"מחוברת / יש להקיש כדי להציג את האתר"</string>
+ <string name="application_label" msgid="1322847171305285454">"ניהול רשתות"</string>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..e1f76e1
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"キャプティブ ポータルの認証"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"デバイスが認証されてキャプティブ ポータル ネットワークにアクセスできるようになると、通知が表示されます"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ネットワークの場所に関する情報"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"場所に関する情報のページがネットワークにあることを示す通知が表示されます"</string>
+ <string name="connected" msgid="4563643884927480998">"接続完了"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"接続しました。タップしてウェブサイトをご覧ください"</string>
+ <string name="application_label" msgid="1322847171305285454">"ネットワーク マネージャ"</string>
+</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
new file mode 100644
index 0000000..a1d79dd
--- /dev/null
+++ b/res/values-ka/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"ავთენტიკაცია ავტორიზაციის პორტალზე"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"შეტყობინებები გამოჩნდება ავტორიზაციის პორტალის ქსელში მოწყობილობის წარმატებული ავთენტიკაციის შემდეგ"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ქსელის ადგილის შესახებ ინფორმაცია"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"შეტყობინებები გამოჩნდება იმის საჩვენებლად, რომ ქსელს აქვს ადგილის შესახებ ინფორმაციის გვერდი"</string>
+ <string name="connected" msgid="4563643884927480998">"დაკავშირებულია"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"დაკავშირებულია / შეეხეთ ვებსაიტის სანახავად"</string>
+ <string name="application_label" msgid="1322847171305285454">"ქსელის მმართველი"</string>
+</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
new file mode 100644
index 0000000..ce501d2
--- /dev/null
+++ b/res/values-kk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Адаптивті портал аутентификациясы"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Құрылғы адаптивті портал желісінде аутентификацияланған кезде, хабарландыру көрсетіледі."</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Желінің орын туралы ақпараты"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Желінің орын туралы ақпарат беті барын көрсету үшін хабарландыру көрсетіледі."</string>
+ <string name="connected" msgid="4563643884927480998">"Байланыстырылды."</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Байланыстырылған/Веб-сайтты көру үшін түртіңіз."</string>
+ <string name="application_label" msgid="1322847171305285454">"Желі менеджері"</string>
+</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
new file mode 100644
index 0000000..c043152
--- /dev/null
+++ b/res/values-km/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"ការផ្ទៀងផ្ទាត់ច្រកចូលប្រើបណ្ដាញ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"ការជូនដំណឹងបង្ហាញ នៅពេលឧបករណ៍បានផ្ទៀងផ្ទាត់បណ្ដាញច្រកចូលប្រើដោយជោគជ័យ"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ព័ត៌មានអំពីទីតាំងបណ្ដាញ"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ការជូនដំណឹងបង្ហាញ ដើម្បីបង្ហាញថាបណ្ដាញមានទំព័រព័ត៌មានអំពីទីតាំង"</string>
+ <string name="connected" msgid="4563643884927480998">"បានភ្ជាប់"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"បានភ្ជាប់ / ចុចដើម្បីមើលគេហទំព័រ"</string>
+ <string name="application_label" msgid="1322847171305285454">"កម្មវិធីគ្រប់គ្រងបណ្ដាញ"</string>
+</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
new file mode 100644
index 0000000..1e7f7f8
--- /dev/null
+++ b/res/values-kn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"ವೈ-ಫೈ ಪ್ರಾರಂಭ ಪೋರ್ಟಲ್ನ ದೃಢೀಕರಣ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"ಸಾಧನವು ವೈ-ಫೈ ಪ್ರಾರಂಭ ಪೋರ್ಟಲ್ಗೆ ಯಶಸ್ವಿಯಾಗಿ ಕನೆಕ್ಟ್ ಆದಾಗ ಮತ್ತು ದೃಢೀಕರಿಸಿದಾಗ ತೋರಿಸಲಾಗುವ ಅಧಿಸೂಚನೆಗಳು"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ನೆಟ್ವರ್ಕ್ ಸ್ಥಳದ ಮಾಹಿತಿ"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ನೆಟ್ವರ್ಕ್ನಲ್ಲಿ ಸ್ಥಳದ ಮಾಹಿತಿ ನೀಡುವ ಪುಟವಿದೆ ಎಂಬುದನ್ನು ಸೂಚಿಸಲು ಅಧಿಸೂಚನೆಗಳನ್ನು ತೋರಿಸಲಾಗುತ್ತದೆ"</string>
+ <string name="connected" msgid="4563643884927480998">"ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"ಸಂಪರ್ಕಿಸಲಾಗಿದೆ / ವೆಬ್ಸೈಟ್ ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+ <string name="application_label" msgid="1322847171305285454">"ನೆಟ್ವರ್ಕ್ ಮ್ಯಾನೇಜರ್"</string>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..03fb285
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"종속 포털 인증"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"기기가 종속 포털 네트워크에 액세스하도록 인증되었을 때 표시되는 알림입니다."</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"네트워크 장소 정보"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"네트워크에 장소 정보 페이지가 있음을 나타내기 위해 표시되는 알림입니다."</string>
+ <string name="connected" msgid="4563643884927480998">"연결됨"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"연결됨 / 탭하여 웹사이트 보기"</string>
+ <string name="application_label" msgid="1322847171305285454">"네트워크 관리자"</string>
+</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
new file mode 100644
index 0000000..9aec881
--- /dev/null
+++ b/res/values-ky/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Кирүү бетинин аутентификациясы"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Түзмөк тармактын кирүү бети аркылуу аутентификациядан ийгиликтүү өткөндө билдирмелер көрсөтүлөт"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Тармактын жайгашуусу жөнүндө маалымат"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Тармак маалымат барагына киргенде билдирме көрсөтүлөт"</string>
+ <string name="connected" msgid="4563643884927480998">"Туташты"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Туташты / Вебсайтты көрүү үчүн таптаңыз"</string>
+ <string name="application_label" msgid="1322847171305285454">"Тармактарды башкаргыч"</string>
+</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
new file mode 100644
index 0000000..58e4bc2
--- /dev/null
+++ b/res/values-lo/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"ການພິສູດຢືນຢັນຊ່ອງທາງເຂົ້າຮັກສາໄວ້"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"ສະແດງການແຈ້ງເຕືອນເມື່ອອຸປະກອນໄດ້ພິສູດຢືນຢັນຫາເຄືອຂ່າຍຊ່ອງທາງເຂົ້າຮັກສາໄວ້ສຳເລັດ"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ຂໍ້ມູນສະຖານທີ່ເຄືອຂ່າຍ"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ສະແດງການແຈ້ງເຕືອນເພື່ອລະບຸວ່າເຄືອຂ່າຍມີໜ້າຂໍ້ມູນສະຖານທີ່"</string>
+ <string name="connected" msgid="4563643884927480998">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"ເຊື່ອມຕໍ່ແລ້ວ / ແຕະເພື່ອເບິ່ງເວັບໄຊ"</string>
+ <string name="application_label" msgid="1322847171305285454">"ຕົວຈັດການເຄືອຂ່າຍ"</string>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 0000000..18c8921
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Fiksuotojo portalo autentifikavimas"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Pranešimai rodomi, kai įrenginys sėkmingai autentifikuotas naudoti fiksuotųjų portalų tinklą"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Tinklo vietos informacija"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Pranešimai rodomi informuojant, kad tinkle yra vietos informacijos puslapis"</string>
+ <string name="connected" msgid="4563643884927480998">"Prisijungta"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Prisijungta / žr. svetainę palietę"</string>
+ <string name="application_label" msgid="1322847171305285454">"Tinklo tvarkymo įrankis"</string>
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 0000000..8ecaada
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentifikācija caurlaides lapā"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Paziņojumi, kas tiek rādīti, kad ierīcē ir sekmīgi veikta autentifikācija caurlaides lapas tīklā"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Norises vietas informācija"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Paziņojumi par to, ka tīklam ir norises vietas informācijas lapa"</string>
+ <string name="connected" msgid="4563643884927480998">"Izveidots savienojums"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Izveidots savienojums / piesk., lai skatītu vietni"</string>
+ <string name="application_label" msgid="1322847171305285454">"Tīkla pārzinis"</string>
+</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
new file mode 100644
index 0000000..44dfed5
--- /dev/null
+++ b/res/values-mk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Проверка преку портал за проверка"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Се прикажуваат известувања кога уредот успешно ќе изврши проверка на мрежата на порталот за проверка"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Мрежни информации за местото на настанот"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Се прикажуваат известувања за да означат дека мрежата има страница со информации за местото на настанот"</string>
+ <string name="connected" msgid="4563643884927480998">"Поврзан"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Поврзано/допрете за да се прикаже веб-сајтот"</string>
+ <string name="application_label" msgid="1322847171305285454">"Управувач со мрежата"</string>
+</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
new file mode 100644
index 0000000..5eee05b
--- /dev/null
+++ b/res/values-ml/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"ക്യാപ്റ്റീവ് പോർട്ടൽ പരിശോധിക്കൽ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"ക്യാപ്റ്റീവ് പോർട്ടൽ നെറ്റ്വർക്കിൽ ഉപകരണം പരിശോധിച്ചുറപ്പിച്ചാൽ അറിയിപ്പുകൾ കാണിക്കും"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"നെറ്റ്വർക്കിലെ സ്ഥല വിവരങ്ങൾ"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"നെറ്റ്വർക്കിന് സ്ഥല വിവരങ്ങൾക്കുള്ള പേജ് ഉണ്ടെന്ന് സൂചിപ്പിക്കാൻ അറിയിപ്പുകൾ കാണിക്കും"</string>
+ <string name="connected" msgid="4563643884927480998">"കണക്റ്റ് ചെയ്തു"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"കണക്റ്റ് ചെയ്തു / വെബ്സെെറ്റ് കാണാൻ ടാപ്പ് ചെയ്യൂ"</string>
+ <string name="application_label" msgid="1322847171305285454">"നെറ്റ്വർക്ക് മാനേജർ"</string>
+</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
new file mode 100644
index 0000000..10e7870
--- /dev/null
+++ b/res/values-mn/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Дамжих порталын нотолгоо"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Төхөөрөмжийг дамжих порталын сүлжээнд амжилттай баталгаажуулсан үед харуулдаг мэдэгдлүүд"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Сүлжээний байршлын мэдээлэл"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Сүлжээнд байршлын мэдээллийн хуудас байгааг заах зорилгоор харуулдаг мэдэгдэл"</string>
+ <string name="connected" msgid="4563643884927480998">"Холбогдсон"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Холбогдсон / Вэб сайтыг үзэхийн тулд товшино уу"</string>
+ <string name="application_label" msgid="1322847171305285454">"Сүлжээний менежер"</string>
+</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
new file mode 100644
index 0000000..ab23825
--- /dev/null
+++ b/res/values-mr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"कॅप्टिव्ह पोर्टल ऑथेंटिकेशन"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"डिव्हाइस यशस्वीरीत्या कॅप्टिव्ह पोर्टल नेटवर्कमध्ये ऑथेंटिकेट केल्यावर दाखवल्या जाणाऱ्या सूचना"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"नेटवर्कच्या स्थळासंबंधित माहिती"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"नेटवर्कचे स्थळासंबंधित माहितीचे पेज असल्याचे दाखवण्याकरिता सूचना दाखवल्या जातात"</string>
+ <string name="connected" msgid="4563643884927480998">"कनेक्ट केले आहे"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"कनेक्ट केले / वेबसाइट पाहण्यासाठी टॅप करा"</string>
+ <string name="application_label" msgid="1322847171305285454">"नेटवर्क व्यवस्थापक"</string>
+</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
new file mode 100644
index 0000000..e3b7121
--- /dev/null
+++ b/res/values-ms/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Pengesahan portal terbolot"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Pemberitahuan ditunjukkan apabila peranti telah berjaya disahkan ke rangkaian portal terbolot"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Maklumat tempat rangkaian"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Pemberitahuan dipaparkan untuk menunjukkan rangkaian mempunyai halaman maklumat tempat"</string>
+ <string name="connected" msgid="4563643884927480998">"Disambungkan"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Disambungkan / Ketik untuk melihat tapak web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Pengurus rangkaian"</string>
+</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
new file mode 100644
index 0000000..1cd147c
--- /dev/null
+++ b/res/values-my/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal အထောက်အထားစိစစ်ခြင်း"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Captive portal ကွန်ရက်တွင် စက်ကို အောင်မြင်စွာ အထောက်အထားစိစစ်ပြီးသောအခါ အကြောင်းကြားချက်များ ပြသည်"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ကွန်ရက်နေရာအချက်အလက်"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ကွန်ရက်တွင် နေရာအချက်အလက် စာမျက်နှာ ရှိကြောင်း ညွှန်ပြရန် အကြောင်းကြားချက်များ ပြသည်"</string>
+ <string name="connected" msgid="4563643884927480998">"ချိတ်ဆက်ထားသည်"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"ချိတ်ဆက်ထားသည် / ဝဘ်ဆိုက်ကိုကြည့်ရန် တို့ပါ"</string>
+ <string name="application_label" msgid="1322847171305285454">"ကွန်ရက်မန်နေဂျာ"</string>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..9f2a576
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentisering med obligatorisk side"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Varsler som vises når enheten er blitt autentisert på en obligatorisk side for et nettverk"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Nettverkets stedsInformasjon"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Varsler vises for å indikere at nettverket har en side med stedsinformasjon"</string>
+ <string name="connected" msgid="4563643884927480998">"Tilkoblet"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Tilkoblet / trykk for å se nettstedet"</string>
+ <string name="application_label" msgid="1322847171305285454">"Nettverksadministrator"</string>
+</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
new file mode 100644
index 0000000..500d584
--- /dev/null
+++ b/res/values-ne/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"क्याप्टिभ पोर्टलको प्रमाणीकरण"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"यन्त्र क्याप्टिभ पोर्टल नेटवर्कमा सफलतापूर्वक जोडिएको कुरा प्रमाणित भएपछि देखाइने सूचनाहरू"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"नेटवर्कको स्थानसम्बन्धी जानकारी"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"नेटवर्कको स्थानसम्बन्धी जानकारी भएको पृष्ठ रहेको सङ्केत गर्न देखाइने सूचनाहरू"</string>
+ <string name="connected" msgid="4563643884927480998">"जोडिएको छ"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"जोडियो / वेबसाइट हेर्न ट्याप गर्नुहोस्"</string>
+ <string name="application_label" msgid="1322847171305285454">"नेटवर्क व्यवस्थापक"</string>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..527d895
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Verificatie van captive portal"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Er worden meldingen weergegeven als het apparaat is geverifieerd voor een captive portal-netwerk"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Netwerklocatie-informatie"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Er worden meldingen weergegeven om aan te geven dat het netwerk een locatie-informatiepagina heeft"</string>
+ <string name="connected" msgid="4563643884927480998">"Verbonden"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Verbonden / Tik om de website te bekijken"</string>
+ <string name="application_label" msgid="1322847171305285454">"Netwerkbeheer"</string>
+</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
new file mode 100644
index 0000000..a067683
--- /dev/null
+++ b/res/values-or/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"କ୍ୟାପ୍ଟିଭ୍ ପୋର୍ଟାଲ୍ ପ୍ରାମାଣିକତା"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"ଡିଭାଇସଟି ଏକ କ୍ୟାପ୍ଟିଭ୍ ପୋର୍ଟାଲ୍ ନେଟୱାର୍କରେ ସଫଳତାର ସହିତ ପ୍ରାମାଣିକୃତ ହେଲେ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଦେଖାଯାଏ"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ନେଟୱାର୍କ ଭେନ୍ୟୁ ସୂଚନା"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ନେଟୱାର୍କରେ ଏକ ଭେନ୍ୟୁ ସୂଚନା ପୃଷ୍ଠା ଅଛି ବୋଲି ସୂଚିତ କରିବାକୁ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଦେଖାଯାଏ"</string>
+ <string name="connected" msgid="4563643884927480998">"ସଂଯୋଗ କରାଯାଇଛି"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"ସଂଯୁକ୍ତ ଅଛି / ୱେବସାଇଟ୍ ଦେଖିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
+ <string name="application_label" msgid="1322847171305285454">"ନେଟୱାର୍କ ପରିଚାଳକ"</string>
+</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
new file mode 100644
index 0000000..6b9e743
--- /dev/null
+++ b/res/values-pa/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"ਕੈਪਟਿਵ ਪੋਰਟਲ ਪ੍ਰਮਾਣੀਕਰਨ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"ਡੀਵਾਈਸ ਦੇ ਸਫਲਤਾਪੂਰਵਕ ਕਨੈਕਟ ਹੋਣ ਅਤੇ ਕੈਪਟਿਵ ਪੋਰਟਲ ਨੈੱਟਵਰਕ \'ਤੇ ਪ੍ਰਮਾਣਿਤ ਹੋਣ \'ਤੇ ਸੂਚਨਾਵਾਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ਨੈੱਟਵਰਕ ਸਥਾਨ ਬਾਰੇ ਜਾਣਕਾਰੀ"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ਇਹ ਦੱਸਣ ਲਈ ਸੂਚਨਾਵਾਂ ਦਿਖਾਈਆਂ ਜਾਂਦੀਆਂ ਹਨ ਕਿ ਨੈੱਟਵਰਕ ਕੋਲ ਸਥਾਨ ਬਾਰੇ ਜਾਣਕਾਰੀ ਵਾਲਾ ਪੰਨਾ ਹੈ"</string>
+ <string name="connected" msgid="4563643884927480998">"ਕਨੈਕਟ ਹੈ"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ / ਵੈੱਬਸਾਈਟ ਨੂੰ ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
+ <string name="application_label" msgid="1322847171305285454">"ਨੈੱਟਵਰਕ ਪ੍ਰਬੰਧਕ"</string>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..7db7b79
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Uwierzytelnianie – portal przechwytujący"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Powiadomienia są wyświetlane, gdy urządzenie zostanie uwierzytelnione w sieci portalu przechwytującego"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Sieć z informacjami o miejscach spotkań"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Powiadomienia są wyświetlane, aby poinformować, że sieć posiada stronę z informacjami o miejscach wydarzeń"</string>
+ <string name="connected" msgid="4563643884927480998">"Połączono"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Połączono / Kliknij, aby wyświetlić stronę"</string>
+ <string name="application_label" msgid="1322847171305285454">"Menedżer sieci"</string>
+</resources>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..e1f090b
--- /dev/null
+++ b/res/values-pt-rBR/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticação de portal cativo"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificações mostradas quando o dispositivo é autenticado em uma rede de portal cativo"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informações sobre o local da rede"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificações mostradas para indicar que a rede tem uma página de informações sobre o local"</string>
+ <string name="connected" msgid="4563643884927480998">"Conectado"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Conectado / Toque para ver o site"</string>
+ <string name="application_label" msgid="1322847171305285454">"Gerenciador de rede"</string>
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..5049b5f
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticação no portal cativo"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificações apresentadas quando o dispositivo tiver sido autenticado com êxito numa rede de portal cativo."</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informações sobre o local da rede"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"As notificações são apresentadas para indicar que a rede tem uma página de informações sobre o local."</string>
+ <string name="connected" msgid="4563643884927480998">"Ligado"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Ligado/Toque para ver o Website"</string>
+ <string name="application_label" msgid="1322847171305285454">"Gestor de redes"</string>
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000..e1f090b
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autenticação de portal cativo"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificações mostradas quando o dispositivo é autenticado em uma rede de portal cativo"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informações sobre o local da rede"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificações mostradas para indicar que a rede tem uma página de informações sobre o local"</string>
+ <string name="connected" msgid="4563643884927480998">"Conectado"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Conectado / Toque para ver o site"</string>
+ <string name="application_label" msgid="1322847171305285454">"Gerenciador de rede"</string>
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 0000000..feda547
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentificare cu portal captiv"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notificări afișate atunci când dispozitivul se autentifică la o rețea cu portal captiv"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informații despre locația rețelei"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notificări afișate pentru a indica faptul că o rețea are o pagină cu informații despre locație"</string>
+ <string name="connected" msgid="4563643884927480998">"Conectată"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Conectat / atingeți pentru a vedea site-ul"</string>
+ <string name="application_label" msgid="1322847171305285454">"Manager de rețea"</string>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..399d025
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Аутентификация на странице входа"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Уведомления, которые появляются при успешной аутентификации устройства на странице входа в сеть."</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Информация о месте, где доступна сеть"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Уведомления о наличии страницы с информацией о месте, где доступна сеть."</string>
+ <string name="connected" msgid="4563643884927480998">"Подключение выполнено"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Подключено. Нажмите, чтобы открыть сайт."</string>
+ <string name="application_label" msgid="1322847171305285454">"Менеджер сетей"</string>
+</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
new file mode 100644
index 0000000..cc91f09
--- /dev/null
+++ b/res/values-si/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"පිවිසුම් දොරටු සත්යාපනය"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"උපාංගය සාර්ථකව පිවිසුම් දොරටු ජාලයකට සත්යාපනය කර ඇති විට දැනුම් දීම් පෙන්වයි"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ජාල ස්ථාන තොරතුරු"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"ජාලයට ස්ථාන තොරතුරු පිටුවක් ඇති බව දැක්වීමට දැනුම් දීම් පෙන්වයි"</string>
+ <string name="connected" msgid="4563643884927480998">"සම්බන්ධ විය"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"සම්බන්ධිතයි / වෙබ් අඩවිය බැලීමට තට්ටු කරන්න"</string>
+ <string name="application_label" msgid="1322847171305285454">"ජාල කළමනාකරු"</string>
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 0000000..819fc57
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Overenie v prihlasovacom portáli"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Zobrazované upozornenia oznamujú, že bolo zariadenie úspešne pripojené k sieti prihlasovacieho portálu a overené"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informácie o mieste konania v sieti"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Zobrazované upozornenia oznamujú, že sieť má stránku s informáciami o mieste konania"</string>
+ <string name="connected" msgid="4563643884927480998">"Pripojená"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Pripojené / klepnutím zobrazíte web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Správca siete"</string>
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 0000000..98b1d9f
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Preverjanje pristnosti za prestrezni portal"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Obvestila, ki so prikazana, ko naprava uspešno vzpostavi povezavo z omrežjem prestreznega portala in preveri pristnost"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informacije o prizorišču v omrežju"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Obvestila, ki so prikazana, ko ima omrežje stran z informacijami o prizorišču"</string>
+ <string name="connected" msgid="4563643884927480998">"Povezava je vzpostavljena"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Povezano / Dotaknite se za ogled spletnega mesta"</string>
+ <string name="application_label" msgid="1322847171305285454">"Upravitelj omrežij"</string>
+</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
new file mode 100644
index 0000000..a590ee6
--- /dev/null
+++ b/res/values-sq/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Vërtetimi i portalit të izoluar"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Njoftimet shfaqen kur pajisja është vërtetuar me sukses në një rrjet të një portali të izoluar"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Informacionet për vendin e rrjetit"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Njoftimet e shfaqura për të treguar se rrjeti ka një faqe me informacione për vendin"</string>
+ <string name="connected" msgid="4563643884927480998">"U lidh"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Lidhur / Trokit për të parë faqen e internetit"</string>
+ <string name="application_label" msgid="1322847171305285454">"Menaxheri i rrjetit"</string>
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 0000000..e791575
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Потврда идентитета на улазном порталу"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Обавештења која се приказују када се потврди идентитет уређаја на мрежи улазног портала"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Информације о месту на мрежи"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Обавештења која се приказују да би показала да мрежа има страницу са информацијама о месту"</string>
+ <string name="connected" msgid="4563643884927480998">"Повезано"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Повезано/додирните да бисте прегледали веб-сајт"</string>
+ <string name="application_label" msgid="1322847171305285454">"Менаџер мреже"</string>
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 0000000..dcdc771
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Autentisering för infångstportal"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Aviseringar visas när enheten har autentiserats i ett nätverk med infångstportal"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Information för nätverkets plats"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Aviseringar visas när det finns en informationssida för nätverkets plats"</string>
+ <string name="connected" msgid="4563643884927480998">"Ansluten"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Ansluten – tryck för att visa webbplatsen"</string>
+ <string name="application_label" msgid="1322847171305285454">"Nätverksadministratör"</string>
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 0000000..480d89c
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Uthibitishaji wa ukurasa wa mwanzo"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Arifa zinaonyeshwa wakati kifaa kimethibitishwa kwenye mtandao wenye ukurasa wa mwanzo"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Maelezo ya mtandao wa mahali"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Arifa zinaonyeshwa ili kuashiria kuwa mtandao una ukurasa wa maelezo ya mahali"</string>
+ <string name="connected" msgid="4563643884927480998">"Umeunganisha"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Imeunganishwa / Gusa ili uangalie tovuti"</string>
+ <string name="application_label" msgid="1322847171305285454">"Kidhibiti cha mtandao"</string>
+</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
new file mode 100644
index 0000000..22b3547
--- /dev/null
+++ b/res/values-ta/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"கேப்டிவ் போர்டல் அங்கீகரிப்பு"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"கேப்டிவ் போர்டல் நெட்வொர்க்கில் சாதனம் வெற்றிகரமாக அங்கீகரிக்கப்பட்டதும் அறிவிப்புகள் காட்டப்படும்"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"நெட்வொர்க் முகவரித் தகவல்"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"நெட்வொர்க்கிற்கு முகவரித் தகவல் பக்கம் இருப்பதைக் குறிப்பிட அறிவிப்புகள் காட்டப்படும்"</string>
+ <string name="connected" msgid="4563643884927480998">"இணைக்கப்பட்டது"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"இணைக்கப்பட்டுள்ளது/இணையதளத்தைப் பார்க்க, தட்டவும்"</string>
+ <string name="application_label" msgid="1322847171305285454">"நெட்வொர்க் நிர்வாகி"</string>
+</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
new file mode 100644
index 0000000..8e85ac7
--- /dev/null
+++ b/res/values-te/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"క్యాప్టివ్ పోర్టల్ ప్రామాణీకరణ"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"క్యాప్టివ్ పోర్టల్ నెట్వర్క్కి పరికరం విజయవంతంగా ప్రామాణీకరించబడినప్పుడు చూపించబడే నోటిఫికేషన్లు"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"నెట్వర్క్ వేదిక సమాచారం"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"నెట్వర్క్లో వేదిక సమాచార పేజీ ఉందని సూచించడానికి నోటిఫికేషన్లు చూపబడతాయి"</string>
+ <string name="connected" msgid="4563643884927480998">"కనెక్ట్ చేయబడింది"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"కనెక్టెడ్ / వెబ్సైట్ను చూడటానికి ట్యాప్ చేయండి."</string>
+ <string name="application_label" msgid="1322847171305285454">"నెట్వర్క్ మేనేజర్"</string>
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 0000000..3b053aa
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"การตรวจสอบสิทธิ์แคพทีฟพอร์ทัล"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"การแจ้งเตือนที่แสดงเมื่ออุปกรณ์ผ่านการตรวจสอบสิทธิ์สำหรับการเข้าถึงเครือข่ายแคพทีฟพอร์ทัลได้สำเร็จ"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"ข้อมูลสถานที่ของเครือข่าย"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"การแจ้งเตือนที่แสดงเพื่อบ่งชี้ว่าเครือข่ายมีหน้าข้อมูลสถานที่"</string>
+ <string name="connected" msgid="4563643884927480998">"เชื่อมต่อแล้ว"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"เชื่อมต่อแล้ว/แตะเพื่อดูเว็บไซต์"</string>
+ <string name="application_label" msgid="1322847171305285454">"ตัวจัดการเครือข่าย"</string>
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 0000000..912ec8a
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Pag-authenticate ng captive portal"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Ipapakita ang mga notification kapag matagumpay na na-authenticate ang device sa isang captive portal network"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Impormasyon ng venue ng network"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Nagpapakita ng mga notification para isaad na may page ng impormasyon ng venue ang network"</string>
+ <string name="connected" msgid="4563643884927480998">"Nakakonekta"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Nakakonekta / I-tap para makita ang website"</string>
+ <string name="application_label" msgid="1322847171305285454">"Network manager"</string>
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 0000000..f89016a
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Giriş portalı kimlik doğrulaması"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Cihaz, giriş portalı ağında başarıyla kimlik doğrulaması yaptığında gösterilen bildirimler"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Ağ mekan bilgileri"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Ağın bir mekan bilgisi sayfası olduğunu gösteren bildirimler"</string>
+ <string name="connected" msgid="4563643884927480998">"Bağlandı"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Bağlı / Web sitesini görüntülemek için dokunun"</string>
+ <string name="application_label" msgid="1322847171305285454">"Ağ yöneticisi"</string>
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 0000000..5ea1b1f
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Автентифікація на адаптивному порталі"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Сповіщення, які з\'являються, коли пристрій авторизується в мережі з адаптивним порталом"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Інформація про місце в мережі"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Сповіщення з\'являються, якщо в мережі є сторінка з інформацією про місце"</string>
+ <string name="connected" msgid="4563643884927480998">"Підключено"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Підключено. Торкніться, щоб переглянути веб-сайт"</string>
+ <string name="application_label" msgid="1322847171305285454">"Менеджер мереж"</string>
+</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
new file mode 100644
index 0000000..ae54a54
--- /dev/null
+++ b/res/values-ur/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"کیپٹو پورٹل کی تصدیق"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"جب آلہ کامیابی کے ساتھ ایک کیپٹو پورٹل نیٹ ورک پر تصدیق کرتا ہے تو اطلاعات کو دکھایا جاتا ہے"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"نیٹ ورک کے مقام کی معلومات"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"نیٹ ورک میں مقام سے متعلق معلومات کا صفحہ موجود ہے کی نشاندہی کے لیے اطلاعات کو دکھایا جاتا ہے"</string>
+ <string name="connected" msgid="4563643884927480998">"منسلک ہے"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"منسلک ہے / ویب سائٹ ملاحظہ کرنے کیلئے تھپتھپائیں"</string>
+ <string name="application_label" msgid="1322847171305285454">"نیٹ ورک مینیجر"</string>
+</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
new file mode 100644
index 0000000..068b2d9
--- /dev/null
+++ b/res/values-uz/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Kirish portali autentifikatsiyasi"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Qurilma tarmoqqa kirish portaliga muvaffaqiyatli autentifikatsiya qilinganida chiqadigan bildirishnomalar"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Qayerda tarmoq mavjudligi haqida axborot"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Tarmoq bor joy haqida axborot sahifasi borligini bildiruvchi bildirishnomalar"</string>
+ <string name="connected" msgid="4563643884927480998">"Ulandi"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Ulangan / Saytni ochish uchun bosing"</string>
+ <string name="application_label" msgid="1322847171305285454">"Tarmoqlar menejeri"</string>
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 0000000..40584e2
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Xác thực qua trang xác thực"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Thông báo hiển thị khi thiết bị đã xác thực thành công và kết nối với mạng của trang xác thực"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Thông tin về địa điểm trong mạng"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Thông báo hiển thị để cho biết mạng này có trang thông tin về địa điểm"</string>
+ <string name="connected" msgid="4563643884927480998">"Đã kết nối"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Đã kết nối/Nhấn để xem trang web"</string>
+ <string name="application_label" msgid="1322847171305285454">"Trình quản lý mạng"</string>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..1157f38
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"强制门户身份验证"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"当设备成功通过强制门户网络的身份验证时显示的通知"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"网络包含场地信息页面"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"为说明网络包含场地信息页面而显示的通知"</string>
+ <string name="connected" msgid="4563643884927480998">"已连接"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"已连接/点按即可查看网站"</string>
+ <string name="application_label" msgid="1322847171305285454">"网络管理器"</string>
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..b08ea64
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"強制網絡入口驗證"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"當裝置成功通過強制網絡入口網絡驗證時,系統就會顯示通知"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"網絡場地資料"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"為說明網絡具有場地資料頁面而顯示的通知"</string>
+ <string name="connected" msgid="4563643884927480998">"已連線"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"已連接/輕按即可查看網站"</string>
+ <string name="application_label" msgid="1322847171305285454">"網絡管理員"</string>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..882f4bf
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"網頁認證入口驗證"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"當裝置成功通過網頁認證入口網路驗證時,系統就會顯示這類通知"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"網路驗證資訊"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"當網路有驗證資訊頁面時,系統就會顯示這類通知"</string>
+ <string name="connected" msgid="4563643884927480998">"已連線"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"已連線/輕觸即可查看網站"</string>
+ <string name="application_label" msgid="1322847171305285454">"網路管理工具"</string>
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 0000000..0e24519
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Ukugunyaza iphothali yabathunjiweyo"</string>
+ <string name="notification_channel_description_connected" msgid="7239184168268014518">"Izaziso eziboniswa lapho idivayisi igunyazwe ngokuphumelelayo kunethiwekhi yephothali yabathunjiweyo"</string>
+ <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Ulwazi lwendawo yenethiwekhi"</string>
+ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Izaziso eziboniswa ukubonisa ukuthi inethiwekhi inekhasi lolwazi lwendawo"</string>
+ <string name="connected" msgid="4563643884927480998">"Ixhunyiwe"</string>
+ <string name="tap_for_info" msgid="6849746325626883711">"Ixhunyiwe / Thepha ukuze ubuke iwebhusayithi"</string>
+ <string name="application_label" msgid="1322847171305285454">"Umphathi wenethiwekhi"</string>
+</resources>
diff --git a/src/android/net/captiveportal/CapportApiProbeResult.java b/src/android/net/captiveportal/CapportApiProbeResult.java
index f693bed..e35b791 100644
--- a/src/android/net/captiveportal/CapportApiProbeResult.java
+++ b/src/android/net/captiveportal/CapportApiProbeResult.java
@@ -25,11 +25,12 @@
* @hide
*/
public class CapportApiProbeResult extends CaptivePortalProbeResult {
- @NonNull
+ // CaptivePortalData may be null if the capport API does not send any valid reply.
+ @Nullable
private final CaptivePortalDataShim mCapportData;
public CapportApiProbeResult(@NonNull CaptivePortalProbeResult result,
- @NonNull CaptivePortalDataShim capportData) {
+ @Nullable CaptivePortalDataShim capportData) {
this(result.mHttpResponseCode, result.redirectUrl, result.detectUrl, capportData,
result.probeType);
}
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 011cbaf..4fedf30 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -80,6 +80,7 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
+import android.stats.connectivity.DhcpFeature;
import android.system.ErrnoException;
import android.system.Os;
import android.util.EventLog;
@@ -101,6 +102,7 @@
import com.android.networkstack.apishim.SocketUtilsShimImpl;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.arp.ArpPacket;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -151,9 +153,10 @@
// Timers and timeouts.
private static final int SECONDS = 1000;
- private static final int FIRST_TIMEOUT_MS = 2 * SECONDS;
- private static final int MAX_TIMEOUT_MS = 128 * SECONDS;
+ private static final int FIRST_TIMEOUT_MS = 1 * SECONDS;
+ private static final int MAX_TIMEOUT_MS = 512 * SECONDS;
private static final int IPMEMORYSTORE_TIMEOUT_MS = 1 * SECONDS;
+ private static final int DHCP_INITREBOOT_TIMEOUT_MS = 5 * SECONDS;
// The waiting time to restart the DHCP configuration process after broadcasting a
// DHCPDECLINE message, (RFC2131 3.1.5 describes client SHOULD wait a minimum of 10
@@ -200,8 +203,8 @@
// This is not strictly needed, since the client is asynchronous and implements exponential
// backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
- // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
- // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
+ // a blocking operation with a 30-second timeout. We pick 18 seconds so we can send packets at
+ // t=0, t=1, t=3, t=7, t=16, allowing for 10% jitter.
private static final int DHCP_TIMEOUT_MS = 36 * SECONDS;
// DhcpClient uses IpClient's handler.
@@ -304,6 +307,8 @@
private final Context mContext;
private final Random mRandom;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+ @NonNull
+ private final IpProvisioningMetrics mMetrics;
// We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
// be off-link as well as on-link).
@@ -377,9 +382,11 @@
*/
public static class Dependencies {
private final NetworkStackIpMemoryStore mNetworkStackIpMemoryStore;
+ private final IpProvisioningMetrics mMetrics;
- public Dependencies(NetworkStackIpMemoryStore store) {
+ public Dependencies(NetworkStackIpMemoryStore store, IpProvisioningMetrics metrics) {
mNetworkStackIpMemoryStore = store;
+ mMetrics = metrics;
}
/**
@@ -406,6 +413,13 @@
}
/**
+ * Get a IpProvisioningMetrics instance.
+ */
+ public IpProvisioningMetrics getIpProvisioningMetrics() {
+ return mMetrics;
+ }
+
+ /**
* Return whether a feature guarded by a feature flag is enabled.
* @see NetworkStackUtils#isFeatureEnabled(Context, String, String)
*/
@@ -443,6 +457,7 @@
mController = controller;
mIfaceName = iface;
mIpMemoryStore = deps.getIpMemoryStore();
+ mMetrics = deps.getIpProvisioningMetrics();
// CHECKSTYLE:OFF IndentationCheck
addState(mStoppedState);
@@ -483,6 +498,7 @@
final boolean sendHostname = deps.getSendHostnameOption(context);
mHostname = sendHostname ? new HostnameTransliterator().transliterate(
deps.getDeviceName(mContext)) : null;
+ mMetrics.setHostnameTransinfo(sendHostname, mHostname != null);
}
public void registerForPreDhcpNotification() {
@@ -502,9 +518,9 @@
* check whether or not to support caching the last lease info and INIT-REBOOT state.
*
* INIT-REBOOT state is supported on Android R by default if there is no experiment flag set to
- * disable this feature explicitly, meanwhile we still hope to be able to control this feature
- * on/off by pushing experiment flag for A/B testing and metrics collection on both of Android
- * Q and R version, however it's disbled on Android Q by default.
+ * disable this feature explicitly, meanwhile turning this feature on/off by pushing experiment
+ * flag makes it possible to do A/B test and metrics collection on both of Android Q and R, but
+ * it's disabled on Android Q by default.
*/
public boolean isDhcpLeaseCacheEnabled() {
final boolean defaultEnabled =
@@ -528,6 +544,15 @@
false /* defaultEnabled */);
}
+ private void recordMetricEnabledFeatures() {
+ if (isDhcpLeaseCacheEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
+ if (isDhcpRapidCommitEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
+ if (isDhcpIpConflictDetectEnabled()) mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+ if (mConfiguration.isPreconnectionEnabled) {
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_FILS);
+ }
+ }
+
private void confirmDhcpLease(DhcpPacket packet, DhcpResults results) {
setDhcpLeaseExpiry(packet);
acceptDhcpResults(results, "Confirmed");
@@ -609,6 +634,7 @@
EventLog.writeEvent(snetTagId, bugId, uid, data);
}
mMetricsLog.log(mIfaceName, new DhcpErrorEvent(e.errorCode));
+ mMetrics.addDhcpErrorCode(e.errorCode);
}
}
@@ -686,6 +712,7 @@
final ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
DO_UNICAST, getRequestedParams(), isDhcpRapidCommitEnabled(), mHostname);
+ mMetrics.incrementCountForDiscover();
return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
}
@@ -704,6 +731,7 @@
String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
" request=" + requestedAddress.getHostAddress() +
" serverid=" + serverStr;
+ mMetrics.incrementCountForRequest();
return transmitPacket(packet, description, encap, to);
}
@@ -936,6 +964,7 @@
} else {
startInitRebootOrInit();
}
+ recordMetricEnabledFeatures();
return HANDLED;
default:
return NOT_HANDLED;
@@ -1421,6 +1450,7 @@
try {
final ArpPacket packet = ArpPacket.parseArpPacket(recvbuf, length);
if (hasIpAddressConflict(packet, mTargetIp)) {
+ mMetrics.incrementCountForIpConflict();
sendMessage(EVENT_IP_CONFLICT);
}
} catch (ArpPacket.ParseException e) {
@@ -1814,6 +1844,7 @@
class DhcpInitRebootState extends DhcpRequestingState {
@Override
public void enter() {
+ mTimeout = DHCP_INITREBOOT_TIMEOUT_MS;
super.enter();
startNewTransaction();
}
diff --git a/src/android/net/dhcp/DhcpLease.java b/src/android/net/dhcp/DhcpLease.java
index 3226f28..0b9cd7c 100644
--- a/src/android/net/dhcp/DhcpLease.java
+++ b/src/android/net/dhcp/DhcpLease.java
@@ -16,7 +16,7 @@
package android.net.dhcp;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import android.net.MacAddress;
import android.os.SystemClock;
diff --git a/src/android/net/dhcp/DhcpLeaseRepository.java b/src/android/net/dhcp/DhcpLeaseRepository.java
index b7a2572..8420996 100644
--- a/src/android/net/dhcp/DhcpLeaseRepository.java
+++ b/src/android/net/dhcp/DhcpLeaseRepository.java
@@ -18,10 +18,10 @@
import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER;
import static android.net.dhcp.DhcpLease.inet4AddrToString;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS;
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java
index d022973..3915740 100644
--- a/src/android/net/dhcp/DhcpPacket.java
+++ b/src/android/net/dhcp/DhcpPacket.java
@@ -22,7 +22,6 @@
import android.net.DhcpResults;
import android.net.LinkAddress;
import android.net.metrics.DhcpErrorEvent;
-import android.net.shared.Inet4AddressUtils;
import android.os.Build;
import android.os.SystemProperties;
import android.system.OsConstants;
@@ -31,6 +30,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.net.module.util.Inet4AddressUtils;
import com.android.networkstack.apishim.common.ShimUtils;
import java.io.UnsupportedEncodingException;
diff --git a/common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java b/src/android/net/dhcp/DhcpResultsParcelableUtil.java
similarity index 68%
rename from common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java
rename to src/android/net/dhcp/DhcpResultsParcelableUtil.java
index 7ef764b..7075925 100644
--- a/common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java
+++ b/src/android/net/dhcp/DhcpResultsParcelableUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,22 +14,22 @@
* limitations under the License.
*/
-package android.net.shared;
+package android.net.dhcp;
-import android.annotation.Nullable;
+import static android.net.shared.IpConfigurationParcelableUtil.parcelAddress;
+import static android.net.shared.IpConfigurationParcelableUtil.unparcelAddress;
+
import android.net.DhcpResults;
import android.net.DhcpResultsParcelable;
-import android.net.InetAddresses;
+
+import androidx.annotation.Nullable;
import java.net.Inet4Address;
-import java.net.InetAddress;
/**
- * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient
- * configuration classes.
- * @hide
+ * A utility class to convert DhcpResults to DhcpResultsParcelable.
*/
-public final class IpConfigurationParcelableUtil {
+public final class DhcpResultsParcelableUtil {
/**
* Convert DhcpResults to a DhcpResultsParcelable.
*/
@@ -60,22 +60,4 @@
results.captivePortalApiUrl = p.captivePortalApiUrl;
return results;
}
-
- /**
- * Convert InetAddress to String.
- * TODO: have an InetAddressParcelable
- */
- public static String parcelAddress(@Nullable InetAddress addr) {
- if (addr == null) return null;
- return addr.getHostAddress();
- }
-
- /**
- * Convert String to InetAddress.
- * TODO: have an InetAddressParcelable
- */
- public static InetAddress unparcelAddress(@Nullable String addr) {
- if (addr == null) return null;
- return InetAddresses.parseNumericAddress(addr);
- }
}
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java
index 55b1f28..6c95b5a 100644
--- a/src/android/net/dhcp/DhcpServer.java
+++ b/src/android/net/dhcp/DhcpServer.java
@@ -23,8 +23,6 @@
import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.system.OsConstants.AF_INET;
@@ -36,6 +34,8 @@
import static android.system.OsConstants.SO_REUSEADDR;
import static com.android.internal.util.TrafficStatsConstants.TAG_SYSTEM_DHCP_SERVER;
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
diff --git a/src/android/net/dhcp/DhcpServingParams.java b/src/android/net/dhcp/DhcpServingParams.java
index 77cdf7a..97db6ae 100644
--- a/src/android/net/dhcp/DhcpServingParams.java
+++ b/src/android/net/dhcp/DhcpServingParams.java
@@ -16,9 +16,8 @@
package android.net.dhcp;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU;
import static com.android.server.util.NetworkStackConstants.IPV4_MIN_MTU;
@@ -27,12 +26,13 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
-import android.net.shared.Inet4AddressUtils;
import android.util.ArraySet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.net.module.util.Inet4AddressUtils;
+
import java.net.Inet4Address;
import java.util.Arrays;
import java.util.Collections;
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 4860ff3..c4f46ae 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -17,7 +17,7 @@
package android.net.ip;
import static android.net.RouteInfo.RTN_UNICAST;
-import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static com.android.server.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
@@ -60,11 +60,11 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
+import android.stats.connectivity.DisconnectCode;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
import android.util.Pair;
-import android.util.Patterns;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -80,12 +80,15 @@
import com.android.networkstack.apishim.NetworkInformationShimImpl;
import com.android.networkstack.apishim.common.NetworkInformationShim;
import com.android.networkstack.apishim.common.ShimUtils;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -128,6 +131,7 @@
private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
private final NetworkStackIpMemoryStore mIpMemoryStore;
private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance();
+ private final IpProvisioningMetrics mIpProvisioningMetrics = new IpProvisioningMetrics();
/**
* Dump all state machine and connectivity packet logs to the specified writer.
@@ -385,6 +389,9 @@
private static final int CMD_COMPLETE_PRECONNECTION = 16;
private static final int CMD_UPDATE_L2INFORMATION = 17;
+ private static final int ARG_LINKPROP_CHANGED_LINKSTATE_DOWN = 0;
+ private static final int ARG_LINKPROP_CHANGED_LINKSTATE_UP = 1;
+
// Internal commands to use instead of trying to call transitionTo() inside
// a given State's enter() method. Calling transitionTo() from enter/exit
// encounters a Log.wtf() that can cause trouble on eng builds.
@@ -481,6 +488,7 @@
private boolean mMulticastFiltering;
private long mStartTimeMillis;
private MacAddress mCurrentBssid;
+ private boolean mHasDisabledIPv6OnProvLoss;
/**
* Reading the snapshot is an asynchronous operation initiated by invoking
@@ -525,8 +533,8 @@
* Get a DhcpClient Dependencies instance.
*/
public DhcpClient.Dependencies getDhcpClientDependencies(
- NetworkStackIpMemoryStore ipMemoryStore) {
- return new DhcpClient.Dependencies(ipMemoryStore);
+ NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) {
+ return new DhcpClient.Dependencies(ipMemoryStore, metrics);
}
/**
@@ -591,7 +599,9 @@
mLinkObserver = new IpClientLinkObserver(
mContext, getHandler(),
mInterfaceName,
- () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED),
+ (ifaceUp) -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED, ifaceUp
+ ? ARG_LINKPROP_CHANGED_LINKSTATE_UP
+ : ARG_LINKPROP_CHANGED_LINKSTATE_DOWN),
config, mLog) {
@Override
public void onInterfaceAdded(String iface) {
@@ -814,9 +824,12 @@
* Stop this IpClient.
*
* <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+ * The message "arg1" parameter is used to record the disconnect code metrics.
+ * Usually this method is called by the peer (e.g. wifi) intentionally to stop IpClient,
+ * consider that's the normal user termination.
*/
public void stop() {
- sendMessage(CMD_STOP);
+ sendMessage(CMD_STOP, DisconnectCode.DC_NORMAL_TERMINATION.getNumber());
}
/**
@@ -1070,6 +1083,12 @@
mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
}
+ // Record the DisconnectCode and transition to StoppingState.
+ private void transitionToStoppingState(final DisconnectCode code) {
+ mIpProvisioningMetrics.setDisconnectCode(code);
+ transitionTo(mStoppingState);
+ }
+
// For now: use WifiStateMachine's historical notion of provisioned.
@VisibleForTesting
static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
@@ -1136,9 +1155,9 @@
// Note that we can still be disconnected by IpReachabilityMonitor
// if the IPv6 default gateway (but not the IPv6 DNS servers; see
// accompanying code in IpReachabilityMonitor) is unreachable.
- final boolean ignoreIPv6ProvisioningLoss =
- mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
- && !mCm.shouldAvoidBadWifi();
+ final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIPv6OnProvLoss
+ || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
+ && !mCm.shouldAvoidBadWifi());
// Additionally:
//
@@ -1162,7 +1181,23 @@
// IPv6 default route then also consider the loss of that default route
// to be a loss of provisioning. See b/27962810.
if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
- delta = PROV_CHANGE_LOST_PROVISIONING;
+ // Although link properties have lost IPv6 default route in this case, if IPv4 is still
+ // working with appropriate routes and DNS servers, we can keep the current connection
+ // without disconnecting from the network, just disable IPv6 on that given network until
+ // to the next provisioning. Disabling IPv6 will result in all IPv6 connectivity torn
+ // down and all IPv6 sockets being closed, the non-routable IPv6 DNS servers will be
+ // stripped out, so applications will be able to reconnect immediately over IPv4. See
+ // b/131781810.
+ if (newLp.isIpv4Provisioned()) {
+ mInterfaceCtrl.disableIPv6();
+ mHasDisabledIPv6OnProvLoss = true;
+ delta = PROV_CHANGE_STILL_PROVISIONED;
+ if (DBG) {
+ mLog.log("Disable IPv6 stack completely when the default router has gone");
+ }
+ } else {
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
}
return delta;
@@ -1263,9 +1298,9 @@
}
final String capportUrl = mDhcpResults.captivePortalApiUrl;
- // Uri.parse does no syntax check; do a simple regex check to eliminate garbage.
+ // Uri.parse does no syntax check; do a simple check to eliminate garbage.
// If the URL is still incorrect data fetching will fail later, which is fine.
- if (capportUrl != null && Patterns.WEB_URL.matcher(capportUrl).matches()) {
+ if (isParseableUrl(capportUrl)) {
NetworkInformationShimImpl.newInstance()
.setCaptivePortalApiUrl(newLp, Uri.parse(capportUrl));
}
@@ -1303,6 +1338,19 @@
return newLp;
}
+ private static boolean isParseableUrl(String url) {
+ // Verify that a URL has a reasonable format that can be parsed as per the URL constructor.
+ // This does not use Patterns.WEB_URL as that pattern excludes URLs without TLDs, such as on
+ // localhost.
+ if (url == null) return false;
+ try {
+ new URL(url);
+ return true;
+ } catch (MalformedURLException e) {
+ return false;
+ }
+ }
+
private static void addAllReachableDnsServers(
LinkProperties lp, Iterable<InetAddress> dnses) {
// TODO: Investigate deleting this reachability check. We should be
@@ -1321,6 +1369,12 @@
if (Objects.equals(newLp, mLinkProperties)) {
return true;
}
+
+ // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
+ // wait for the provisioning completion and record the latency.
+ mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned());
+ mIpProvisioningMetrics.setIPv6ProvisionedLatencyOnFirstTime(newLp.isIpv6Provisioned());
+
final int delta = setLinkProperties(newLp);
// Most of the attributes stored in the memory store are deduced from
// the link properties, therefore when the properties update the memory
@@ -1416,10 +1470,10 @@
}
mCallback.onNewDhcpResults(null);
- handleProvisioningFailure();
+ handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_FAIL);
}
- private void handleProvisioningFailure() {
+ private void handleProvisioningFailure(final DisconnectCode code) {
final LinkProperties newLp = assembleLinkProperties();
int delta = setLinkProperties(newLp);
// If we've gotten here and we're still not provisioned treat that as
@@ -1436,7 +1490,7 @@
dispatchCallback(delta, newLp);
if (delta == PROV_CHANGE_LOST_PROVISIONING) {
- transitionTo(mStoppingState);
+ transitionToStoppingState(code);
}
}
@@ -1577,6 +1631,7 @@
@Override
public void enter() {
stopAllIP();
+ mHasDisabledIPv6OnProvLoss = false;
mLinkObserver.clearInterfaceParams();
resetLinkProperties();
@@ -1691,7 +1746,7 @@
private void startDhcpClient() {
// Start DHCPv4.
mDhcpClient = mDependencies.makeDhcpClient(mContext, IpClient.this, mInterfaceParams,
- mDependencies.getDhcpClientDependencies(mIpMemoryStore));
+ mDependencies.getDhcpClientDependencies(mIpMemoryStore, mIpProvisioningMetrics));
// If preconnection is enabled, there is no need to ask Wi-Fi to disable powersaving
// during DHCP, because the DHCP handshake will happen during association. In order to
@@ -1712,7 +1767,8 @@
if (mInterfaceParams == null) {
logError("Failed to find InterfaceParams for " + mInterfaceName);
doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND);
- deferMessage(obtainMessage(CMD_STOP));
+ deferMessage(obtainMessage(CMD_STOP,
+ DisconnectCode.DC_INTERFACE_NOT_FOUND.getNumber()));
return;
}
@@ -1740,7 +1796,7 @@
case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
handleLinkPropertiesUpdate(NO_CALLBACKS);
if (readyToProceed()) {
- transitionTo(mRunningState);
+ transitionTo(isUsingPreconnection() ? mPreconnectingState : mRunningState);
}
break;
@@ -1804,6 +1860,7 @@
class StartedState extends State {
@Override
public void enter() {
+ mIpProvisioningMetrics.reset();
mStartTimeMillis = SystemClock.elapsedRealtime();
if (mConfiguration.mProvisioningTimeoutMs > 0) {
final long alarmTime = SystemClock.elapsedRealtime()
@@ -1815,13 +1872,17 @@
@Override
public void exit() {
mProvisioningTimeoutAlarm.cancel();
+
+ // Record metrics information once this provisioning has completed due to certain
+ // reason (normal termination, provisioning timeout, lost provisioning and etc).
+ mIpProvisioningMetrics.statsWrite();
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_STOP:
- transitionTo(mStoppingState);
+ transitionToStoppingState(DisconnectCode.forNumber(msg.arg1));
break;
case CMD_UPDATE_L2KEY_CLUSTER: {
@@ -1843,7 +1904,7 @@
break;
case EVENT_PROVISIONING_TIMEOUT:
- handleProvisioningFailure();
+ handleProvisioningFailure(DisconnectCode.DC_PROVISIONING_TIMEOUT);
break;
default:
@@ -1880,13 +1941,13 @@
if (mConfiguration.mEnableIPv6 && !startIPv6()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
- enqueueJumpToStoppingState();
+ enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV6);
return;
}
if (mConfiguration.mEnableIPv4 && !isUsingPreconnection() && !startIPv4()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
- enqueueJumpToStoppingState();
+ enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPV4);
return;
}
@@ -1894,14 +1955,14 @@
if ((config != null) && !applyInitialConfig(config)) {
// TODO introduce a new IpManagerEvent constant to distinguish this error case.
doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
- enqueueJumpToStoppingState();
+ enqueueJumpToStoppingState(DisconnectCode.DC_INVALID_PROVISIONING);
return;
}
if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
doImmediateProvisioningFailure(
IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
- enqueueJumpToStoppingState();
+ enqueueJumpToStoppingState(DisconnectCode.DC_ERROR_STARTING_IPREACHABILITYMONITOR);
return;
}
}
@@ -1933,8 +1994,8 @@
resetLinkProperties();
}
- private void enqueueJumpToStoppingState() {
- deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING));
+ private void enqueueJumpToStoppingState(final DisconnectCode code) {
+ deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING, code.getNumber()));
}
private ConnectivityPacketTracker createPacketTracker() {
@@ -1969,7 +2030,7 @@
switch (msg.what) {
case CMD_JUMP_RUNNING_TO_STOPPING:
case CMD_STOP:
- transitionTo(mStoppingState);
+ transitionToStoppingState(DisconnectCode.forNumber(msg.arg1));
break;
case CMD_START:
@@ -1996,8 +2057,16 @@
break;
case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ // EVENT_NETLINK_LINKPROPERTIES_CHANGED message will be received in both of
+ // provisioning loss and normal user termination cases (e.g. turn off wifi or
+ // switch to another wifi ssid), hence, checking the current interface link
+ // state (down or up) helps distinguish the two cases: if the link state is
+ // down, provisioning is only lost because the link is being torn down (for
+ // example when turning off wifi), so treat it as a normal termination.
if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
- transitionTo(mStoppingState);
+ final boolean linkStateUp = (msg.arg1 == ARG_LINKPROP_CHANGED_LINKSTATE_UP);
+ transitionToStoppingState(linkStateUp ? DisconnectCode.DC_PROVISIONING_FAIL
+ : DisconnectCode.DC_NORMAL_TERMINATION);
}
break;
@@ -2077,7 +2146,7 @@
} else {
logError("Failed to set IPv4 address.");
dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, mLinkProperties);
- transitionTo(mStoppingState);
+ transitionToStoppingState(DisconnectCode.DC_PROVISIONING_FAIL);
}
break;
}
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index dcbca94..82f8d5d 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -89,8 +89,13 @@
public interface Callback {
/**
* Called when some properties of the link were updated.
+ *
+ * @param linkState Whether the interface link state is up as per the latest
+ * {@link #onInterfaceLinkStateChanged(String, boolean)} callback. This
+ * should only be used for metrics purposes, as it could be inconsistent
+ * with {@link #getLinkProperties()} in particular.
*/
- void update();
+ void update(boolean linkState);
}
/** Configuration parameters for IpClientLinkObserver. */
@@ -105,6 +110,7 @@
private final String mInterfaceName;
private final Callback mCallback;
private final LinkProperties mLinkProperties;
+ private boolean mInterfaceLinkState;
private DnsServerRepository mDnsServerRepository;
private final AlarmManager mAlarmManager;
private final Configuration mConfig;
@@ -121,6 +127,7 @@
mLinkProperties = new LinkProperties();
mLinkProperties.setInterfaceName(mInterfaceName);
mConfig = config;
+ mInterfaceLinkState = true; // Assume up by default
mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag);
@@ -148,8 +155,22 @@
// now empty. Note that from the moment that the interface is removed, any further
// interface-specific messages (e.g., RTM_DELADDR) will not reach us, because the netd
// code that parses them will not be able to resolve the ifindex to an interface name.
- clearLinkProperties();
- mCallback.update();
+ final boolean linkState;
+ synchronized (this) {
+ clearLinkProperties();
+ linkState = getInterfaceLinkStateLocked();
+ }
+ mCallback.update(linkState);
+ }
+ }
+
+ @Override
+ public void onInterfaceLinkStateChanged(String iface, boolean state) {
+ if (mInterfaceName.equals(iface)) {
+ maybeLog("interfaceLinkStateChanged", iface + (state ? " up" : " down"));
+ synchronized (this) {
+ setInterfaceLinkStateLocked(state);
+ }
}
}
@@ -157,12 +178,14 @@
public void onInterfaceAddressUpdated(LinkAddress address, String iface) {
if (mInterfaceName.equals(iface)) {
maybeLog("addressUpdated", iface, address);
- boolean changed;
+ final boolean changed;
+ final boolean linkState;
synchronized (this) {
changed = mLinkProperties.addLinkAddress(address);
+ linkState = getInterfaceLinkStateLocked();
}
if (changed) {
- mCallback.update();
+ mCallback.update(linkState);
}
}
}
@@ -171,12 +194,14 @@
public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
if (mInterfaceName.equals(iface)) {
maybeLog("addressRemoved", iface, address);
- boolean changed;
+ final boolean changed;
+ final boolean linkState;
synchronized (this) {
changed = mLinkProperties.removeLinkAddress(address);
+ linkState = getInterfaceLinkStateLocked();
}
if (changed) {
- mCallback.update();
+ mCallback.update(linkState);
}
}
}
@@ -185,12 +210,14 @@
public void onRouteUpdated(RouteInfo route) {
if (mInterfaceName.equals(route.getInterface())) {
maybeLog("routeUpdated", route);
- boolean changed;
+ final boolean changed;
+ final boolean linkState;
synchronized (this) {
changed = mLinkProperties.addRoute(route);
+ linkState = getInterfaceLinkStateLocked();
}
if (changed) {
- mCallback.update();
+ mCallback.update(linkState);
}
}
}
@@ -199,12 +226,14 @@
public void onRouteRemoved(RouteInfo route) {
if (mInterfaceName.equals(route.getInterface())) {
maybeLog("routeRemoved", route);
- boolean changed;
+ final boolean changed;
+ final boolean linkState;
synchronized (this) {
changed = mLinkProperties.removeRoute(route);
+ linkState = getInterfaceLinkStateLocked();
}
if (changed) {
- mCallback.update();
+ mCallback.update(linkState);
}
}
}
@@ -213,12 +242,14 @@
public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
if (mInterfaceName.equals(iface)) {
maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
- boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
+ final boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
+ final boolean linkState;
if (changed) {
synchronized (this) {
mDnsServerRepository.setDnsServersOn(mLinkProperties);
+ linkState = getInterfaceLinkStateLocked();
}
- mCallback.update();
+ mCallback.update(linkState);
}
}
}
@@ -243,6 +274,14 @@
mLinkProperties.setInterfaceName(mInterfaceName);
}
+ private boolean getInterfaceLinkStateLocked() {
+ return mInterfaceLinkState;
+ }
+
+ private void setInterfaceLinkStateLocked(boolean state) {
+ mInterfaceLinkState = state;
+ }
+
/** Notifies this object of new interface parameters. */
public void setInterfaceParams(InterfaceParams params) {
mNetlinkMonitor.setIfindex(params.index);
@@ -355,7 +394,7 @@
cancelPref64Alarm();
}
- mCallback.update();
+ mCallback.update(getInterfaceLinkStateLocked());
}
private void processPref64Option(StructNdOptPref64 opt, final long now) {
diff --git a/src/android/net/util/DataStallUtils.java b/src/android/net/util/DataStallUtils.java
index 3391a71..2c5de57 100644
--- a/src/android/net/util/DataStallUtils.java
+++ b/src/android/net/util/DataStallUtils.java
@@ -96,7 +96,7 @@
/**
* Default polling interval to observe the tcp health.
*/
- public static int DEFAULT_TCP_POLLING_INTERVAL_MS = 10_000;
+ public static int DEFAULT_TCP_POLLING_INTERVAL_MS = 20_000;
/**
* Default tcp packets fail rate to suspect as a data stall.
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 99563ee..94de7c3 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -226,6 +226,15 @@
public static final String DNS_PROBE_PRIVATE_IP_NO_INTERNET_VERSION =
"dns_probe_private_ip_no_internet";
+ /**
+ * Experiment flag to enable validation metrics sent by NetworkMonitor.
+ *
+ * Metrics are sent by default. They can be disabled by setting the flag to a number greater
+ * than the APK version (for example 999999999).
+ * @see #isFeatureEnabled(Context, String, String, boolean)
+ */
+ public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version";
+
static {
System.loadLibrary("networkstackutilsjni");
}
@@ -348,6 +357,9 @@
* {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack}
* with current version of property. If this property version is valid, the corresponding
* experimental feature would be enabled, otherwise disabled.
+ *
+ * This is useful to ensure that if a module install is rolled back, flags are not left fully
+ * rolled out on a version where they have not been well tested.
* @param context The global context information about an app environment.
* @param namespace The namespace containing the property to look up.
* @param name The name of the property to look up.
@@ -363,6 +375,9 @@
* {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack}
* with current version of property. If this property version is valid, the corresponding
* experimental feature would be enabled, otherwise disabled.
+ *
+ * This is useful to ensure that if a module install is rolled back, flags are not left fully
+ * rolled out on a version where they have not been well tested.
* @param context The global context information about an app environment.
* @param namespace The namespace containing the property to look up.
* @param name The name of the property to look up.
@@ -436,4 +451,22 @@
return addr instanceof Inet6Address
&& ((addr.getAddress()[0] & 0xfe) == 0xfc);
}
+
+ /**
+ * Returns the {@code int} nearest in value to {@code value}.
+ *
+ * @param value any {@code long} value
+ * @return the same value cast to {@code int} if it is in the range of the {@code int}
+ * type, {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if
+ * it is too small
+ */
+ public static int saturatedCast(long value) {
+ if (value > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ if (value < Integer.MIN_VALUE) {
+ return Integer.MIN_VALUE;
+ }
+ return (int) value;
+ }
}
diff --git a/src/android/net/util/Stopwatch.java b/src/android/net/util/Stopwatch.java
index 07618e9..88e523e 100644
--- a/src/android/net/util/Stopwatch.java
+++ b/src/android/net/util/Stopwatch.java
@@ -49,6 +49,15 @@
}
/**
+ * Restart the Stopwatch.
+ */
+ public Stopwatch restart() {
+ mStartTimeNs = SystemClock.elapsedRealtimeNanos();
+ mStopTimeNs = 0;
+ return this;
+ }
+
+ /**
* Stop the Stopwatch.
* @return the total time recorded, in microseconds, or 0 if not started.
*/
diff --git a/src/com/android/networkstack/NetworkStackNotifier.java b/src/com/android/networkstack/NetworkStackNotifier.java
index a330c70..dbb62b1 100644
--- a/src/com/android/networkstack/NetworkStackNotifier.java
+++ b/src/com/android/networkstack/NetworkStackNotifier.java
@@ -16,6 +16,8 @@
package com.android.networkstack;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -142,7 +144,19 @@
resources.getString(title),
importance);
channel.setDescription(resources.getString(description));
- mNotificationManager.createNotificationChannel(channel);
+ getNotificationManagerForChannels().createNotificationChannel(channel);
+ }
+
+ /**
+ * Get the NotificationManager to use to query channels, as opposed to posting notifications.
+ *
+ * Although notifications are posted as USER_ALL, notification channels are always created
+ * based on the UID calling NotificationManager, regardless of the context UserHandle.
+ * When querying notification channels, using a USER_ALL context would return no channel: the
+ * default context (as UserHandle 0 for NetworkStack) must be used.
+ */
+ private NotificationManager getNotificationManagerForChannels() {
+ return mContext.getSystemService(NotificationManager.class);
}
/**
@@ -225,7 +239,7 @@
.setContentText(res.getString(R.string.tap_for_info))
.setContentIntent(mDependencies.getActivityPendingIntent(
getContextAsUser(mContext, UserHandle.CURRENT),
- infoIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+ infoIntent, PendingIntent.FLAG_IMMUTABLE));
networkStatus.mShownNotification = NOTE_VENUE_INFO;
} else if (showValidated) {
@@ -238,7 +252,7 @@
.setContentIntent(mDependencies.getActivityPendingIntent(
getContextAsUser(mContext, UserHandle.CURRENT),
new Intent(Settings.ACTION_WIFI_SETTINGS),
- PendingIntent.FLAG_UPDATE_CURRENT));
+ PendingIntent.FLAG_IMMUTABLE));
networkStatus.mShownNotification = NOTE_CONNECTED;
} else {
@@ -284,7 +298,11 @@
}
private boolean isVenueInfoNotificationEnabled() {
- return mNotificationManager.getNotificationChannel(CHANNEL_VENUE_INFO) != null;
+ final NotificationChannel channel = getNotificationManagerForChannels()
+ .getNotificationChannel(CHANNEL_VENUE_INFO);
+ if (channel == null) return false;
+
+ return channel.getImportance() != IMPORTANCE_NONE;
}
private static String getNotificationTag(@NonNull Network network) {
diff --git a/src/com/android/networkstack/metrics/DataStallDetectionStats.java b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
index d294e04..4de2ec0 100644
--- a/src/com/android/networkstack/metrics/DataStallDetectionStats.java
+++ b/src/com/android/networkstack/metrics/DataStallDetectionStats.java
@@ -261,11 +261,13 @@
if (info == null) return DataStallEventProto.AP_BAND_UNKNOWN;
int freq = info.getFrequency();
- // Refer to ScanResult.is5GHz() and ScanResult.is24GHz().
- if (freq > 4900 && freq < 5900) {
+ // Refer to ScanResult.is5GHz(), ScanResult.is24GHz() and ScanResult.is6GHz().
+ if (freq >= 5160 && freq <= 5865) {
return DataStallEventProto.AP_BAND_5GHZ;
- } else if (freq > 2400 && freq < 2500) {
+ } else if (freq >= 2412 && freq <= 2484) {
return DataStallEventProto.AP_BAND_2GHZ;
+ } else if (freq >= 5945 && freq <= 7105) {
+ return DataStallEventProto.AP_BAND_6GHZ;
} else {
return DataStallEventProto.AP_BAND_UNKNOWN;
}
diff --git a/src/com/android/networkstack/metrics/IpProvisioningMetrics.java b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
new file mode 100644
index 0000000..64e173d
--- /dev/null
+++ b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.net.util.NetworkStackUtils;
+import android.net.util.Stopwatch;
+import android.stats.connectivity.DhcpErrorCode;
+import android.stats.connectivity.DhcpFeature;
+import android.stats.connectivity.DisconnectCode;
+import android.stats.connectivity.HostnameTransResult;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class to record the network IpProvisioning into statsd.
+ * 1. Fill in NetworkIpProvisioningReported proto.
+ * 2. Write the NetworkIpProvisioningReported proto into statsd.
+ * 3. This class is not thread-safe, and should always be accessed from the same thread.
+ * @hide
+ */
+
+public class IpProvisioningMetrics {
+ private static final String TAG = IpProvisioningMetrics.class.getSimpleName();
+ private final NetworkIpProvisioningReported.Builder mStatsBuilder =
+ NetworkIpProvisioningReported.newBuilder();
+ private final DhcpSession.Builder mDhcpSessionBuilder = DhcpSession.newBuilder();
+ private final Stopwatch mIpv4Watch = new Stopwatch().start();
+ private final Stopwatch mIpv6Watch = new Stopwatch().start();
+ private final Stopwatch mWatch = new Stopwatch().start();
+ private final Set<DhcpFeature> mDhcpFeatures = new HashSet<DhcpFeature>();
+
+ // Define a maximum number of the DhcpErrorCode.
+ public static final int MAX_DHCP_ERROR_COUNT = 20;
+
+ /**
+ * reset this all metrics members
+ */
+ public void reset() {
+ mStatsBuilder.clear();
+ mDhcpSessionBuilder.clear();
+ mDhcpFeatures.clear();
+ mIpv4Watch.restart();
+ mIpv6Watch.restart();
+ mWatch.restart();
+ }
+
+ /**
+ * Write the TransportType into mStatsBuilder.
+ * TODO: implement this
+ */
+ public void setTransportType() {}
+
+ /**
+ * Write the IPv4Provisioned latency into mStatsBuilder.
+ */
+ public void setIPv4ProvisionedLatencyOnFirstTime(final boolean isIpv4Provisioned) {
+ if (isIpv4Provisioned && !mStatsBuilder.hasIpv4LatencyMicros()) {
+ mStatsBuilder.setIpv4LatencyMicros(NetworkStackUtils.saturatedCast(mIpv4Watch.stop()));
+ }
+ }
+
+ /**
+ * Write the IPv6Provisioned latency into mStatsBuilder.
+ */
+ public void setIPv6ProvisionedLatencyOnFirstTime(final boolean isIpv6Provisioned) {
+ if (isIpv6Provisioned && !mStatsBuilder.hasIpv6LatencyMicros()) {
+ mStatsBuilder.setIpv6LatencyMicros(NetworkStackUtils.saturatedCast(mIpv6Watch.stop()));
+ }
+ }
+
+ /**
+ * Write the DhcpFeature proto into mStatsBuilder.
+ */
+ public void setDhcpEnabledFeature(final DhcpFeature feature) {
+ if (feature == DhcpFeature.DF_UNKNOWN) return;
+ mDhcpFeatures.add(feature);
+ }
+
+ /**
+ * Write the DHCPDISCOVER transmission count into DhcpSession.
+ */
+ public void incrementCountForDiscover() {
+ mDhcpSessionBuilder.setDiscoverCount(mDhcpSessionBuilder.getDiscoverCount() + 1);
+ }
+
+ /**
+ * Write the DHCPREQUEST transmission count into DhcpSession.
+ */
+ public void incrementCountForRequest() {
+ mDhcpSessionBuilder.setRequestCount(mDhcpSessionBuilder.getRequestCount() + 1);
+ }
+
+ /**
+ * Write the IPv4 address conflict count into DhcpSession.
+ */
+ public void incrementCountForIpConflict() {
+ mDhcpSessionBuilder.setConflictCount(mDhcpSessionBuilder.getConflictCount() + 1);
+ }
+
+ /**
+ * Write the hostname transliteration result into DhcpSession.
+ */
+ public void setHostnameTransinfo(final boolean isOptionEnabled, final boolean transSuccess) {
+ mDhcpSessionBuilder.setHtResult(!isOptionEnabled ? HostnameTransResult.HTR_DISABLE :
+ transSuccess ? HostnameTransResult.HTR_SUCCESS : HostnameTransResult.HTR_FAILURE);
+ }
+
+ private static DhcpErrorCode dhcpErrorFromNumberSafe(int number) {
+ // See DhcpErrorCode.errorCodeWithOption
+ // TODO: add a DhcpErrorCode method to extract the code;
+ // or replace legacy error codes with the new metrics.
+ final DhcpErrorCode error = DhcpErrorCode.forNumber(number & 0xFFFF0000);
+ if (error == null) return DhcpErrorCode.ET_UNKNOWN;
+ return error;
+ }
+
+ /**
+ * write the DHCP error code into DhcpSession.
+ */
+ public void addDhcpErrorCode(final int errorCode) {
+ if (mDhcpSessionBuilder.getErrorCodeCount() >= MAX_DHCP_ERROR_COUNT) return;
+ mDhcpSessionBuilder.addErrorCode(dhcpErrorFromNumberSafe(errorCode));
+ }
+
+ /**
+ * Write the IP provision disconnect code into DhcpSession.
+ */
+ public void setDisconnectCode(final DisconnectCode disconnectCode) {
+ if (mStatsBuilder.hasDisconnectCode()) return;
+ mStatsBuilder.setDisconnectCode(disconnectCode);
+ }
+
+ /**
+ * Write the NetworkIpProvisioningReported proto into statsd.
+ */
+ public NetworkIpProvisioningReported statsWrite() {
+ if (!mWatch.isStarted()) return null;
+ for (DhcpFeature feature : mDhcpFeatures) {
+ mDhcpSessionBuilder.addUsedFeatures(feature);
+ }
+ mStatsBuilder.setDhcpSession(mDhcpSessionBuilder);
+ mStatsBuilder.setProvisioningDurationMicros(mWatch.stop());
+ mStatsBuilder.setRandomNumber((int) (Math.random() * 1000));
+ final NetworkIpProvisioningReported Stats = mStatsBuilder.build();
+ final byte[] DhcpSession = Stats.getDhcpSession().toByteArray();
+ NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_IP_PROVISIONING_REPORTED,
+ Stats.getTransportType().getNumber(),
+ Stats.getIpv4LatencyMicros(),
+ Stats.getIpv6LatencyMicros(),
+ Stats.getProvisioningDurationMicros(),
+ Stats.getDisconnectCode().getNumber(),
+ DhcpSession,
+ Stats.getRandomNumber());
+ mWatch.reset();
+ return Stats;
+ }
+}
diff --git a/src/com/android/networkstack/metrics/NetworkValidationMetrics.java b/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
new file mode 100644
index 0000000..f27a939
--- /dev/null
+++ b/src/com/android/networkstack/metrics/NetworkValidationMetrics.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+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_LOWPAN;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+
+import static java.lang.System.currentTimeMillis;
+
+import android.net.INetworkMonitor;
+import android.net.NetworkCapabilities;
+import android.net.captiveportal.CaptivePortalProbeResult;
+import android.net.metrics.ValidationProbeEvent;
+import android.net.util.NetworkStackUtils;
+import android.net.util.Stopwatch;
+import android.stats.connectivity.ProbeResult;
+import android.stats.connectivity.ProbeType;
+import android.stats.connectivity.TransportType;
+import android.stats.connectivity.ValidationResult;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
+/**
+ * Class to record the network validation into statsd.
+ * 1. Fill in NetworkValidationReported proto.
+ * 2. Write the NetworkValidationReported proto into statsd.
+ * @hide
+ */
+
+public class NetworkValidationMetrics {
+ private final NetworkValidationReported.Builder mStatsBuilder =
+ NetworkValidationReported.newBuilder();
+ private final ProbeEvents.Builder mProbeEventsBuilder = ProbeEvents.newBuilder();
+ private final CapportApiData.Builder mCapportApiDataBuilder = CapportApiData.newBuilder();
+ private final Stopwatch mWatch = new Stopwatch();
+ private int mValidationIndex = 0;
+ // Define a maximum size that can store events.
+ public static final int MAX_PROBE_EVENTS_COUNT = 20;
+
+ /**
+ * Reset this NetworkValidationMetrics and start collecting timing and metrics.
+ *
+ * <p>This must be called when validation starts.
+ */
+ public void startCollection(@Nullable NetworkCapabilities nc) {
+ mStatsBuilder.clear();
+ mProbeEventsBuilder.clear();
+ mCapportApiDataBuilder.clear();
+ mWatch.restart();
+ mStatsBuilder.setTransportType(getTransportTypeFromNC(nc));
+ mValidationIndex++;
+ }
+
+ /**
+ * Returns the enum TransportType.
+ *
+ * <p>This method only supports a limited set of common transport type combinations that can be
+ * measured through metrics, and will return {@link TransportType#TT_UNKNOWN} for others. This
+ * ensures that, for example, metrics for a TRANSPORT_NEW_UNKNOWN | TRANSPORT_ETHERNET network
+ * cannot get aggregated with / compared with a "normal" TRANSPORT_ETHERNET network without
+ * noticing.
+ *
+ * @param nc Capabilities to extract transport type from.
+ * @return the TransportType which is defined in
+ * core/proto/android/stats/connectivity/network_stack.proto
+ */
+ @VisibleForTesting
+ public static TransportType getTransportTypeFromNC(@Nullable NetworkCapabilities nc) {
+ if (nc == null) return TransportType.TT_UNKNOWN;
+
+ final int trCount = nc.getTransportTypes().length;
+ boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR);
+ boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI);
+ boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH);
+ boolean hasEthernet = nc.hasTransport(TRANSPORT_ETHERNET);
+ boolean hasVpn = nc.hasTransport(TRANSPORT_VPN);
+ boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE);
+ boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN);
+
+ // VPN networks are not subject to validation and should not see validation stats, but
+ // metrics could be added to measure private DNS probes only.
+ if (trCount == 3 && hasCellular && hasWifi && hasVpn) {
+ return TransportType.TT_WIFI_CELLULAR_VPN;
+ }
+
+ if (trCount == 2 && hasVpn) {
+ if (hasWifi) return TransportType.TT_WIFI_VPN;
+ if (hasCellular) return TransportType.TT_CELLULAR_VPN;
+ if (hasBT) return TransportType.TT_BLUETOOTH_VPN;
+ if (hasEthernet) return TransportType.TT_ETHERNET_VPN;
+ }
+
+ if (trCount == 1) {
+ if (hasWifi) return TransportType.TT_WIFI;
+ if (hasCellular) return TransportType.TT_CELLULAR;
+ if (hasBT) return TransportType.TT_BLUETOOTH;
+ if (hasEthernet) return TransportType.TT_ETHERNET;
+ if (hasWifiAware) return TransportType.TT_WIFI_AWARE;
+ if (hasLopan) return TransportType.TT_LOWPAN;
+ // TODO: consider having a TT_VPN for VPN-only transport
+ }
+
+ return TransportType.TT_UNKNOWN;
+ }
+
+ /**
+ * Map {@link ValidationProbeEvent} to {@link ProbeType}.
+ */
+ public static ProbeType probeTypeToEnum(final int probeType) {
+ switch(probeType) {
+ case ValidationProbeEvent.PROBE_DNS:
+ return ProbeType.PT_DNS;
+ case ValidationProbeEvent.PROBE_HTTP:
+ return ProbeType.PT_HTTP;
+ case ValidationProbeEvent.PROBE_HTTPS:
+ return ProbeType.PT_HTTPS;
+ case ValidationProbeEvent.PROBE_PAC:
+ return ProbeType.PT_PAC;
+ case ValidationProbeEvent.PROBE_FALLBACK:
+ return ProbeType.PT_FALLBACK;
+ case ValidationProbeEvent.PROBE_PRIVDNS:
+ return ProbeType.PT_PRIVDNS;
+ default:
+ return ProbeType.PT_UNKNOWN;
+ }
+ }
+
+ /**
+ * Map {@link CaptivePortalProbeResult} to {@link ProbeResult}.
+ */
+ public static ProbeResult httpProbeResultToEnum(final CaptivePortalProbeResult result) {
+ if (result == null) return ProbeResult.PR_UNKNOWN;
+
+ if (result.isSuccessful()) {
+ return ProbeResult.PR_SUCCESS;
+ } else if (result.isDnsPrivateIpResponse()) {
+ return ProbeResult.PR_PRIVATE_IP_DNS;
+ } else if (result.isFailed()) {
+ return ProbeResult.PR_FAILURE;
+ } else if (result.isPortal()) {
+ return ProbeResult.PR_PORTAL;
+ } else {
+ return ProbeResult.PR_UNKNOWN;
+ }
+ }
+
+ /**
+ * Map validation result (as per INetworkMonitor) to {@link ValidationResult}.
+ */
+ @VisibleForTesting
+ public static ValidationResult validationResultToEnum(int result, String redirectUrl) {
+ // TODO: consider adding a VR_PARTIAL_SUCCESS field to track cases where users accepted
+ // partial connectivity
+ if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID) != 0) {
+ return ValidationResult.VR_SUCCESS;
+ } else if (redirectUrl != null) {
+ return ValidationResult.VR_PORTAL;
+ } else if ((result & INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL) != 0) {
+ return ValidationResult.VR_PARTIAL;
+ } else {
+ return ValidationResult.VR_FAILURE;
+ }
+ }
+
+ /**
+ * Add a network probe event to the metrics builder.
+ */
+ public void addProbeEvent(final ProbeType type, final long durationUs, final ProbeResult result,
+ @Nullable final CaptivePortalDataShim capportData) {
+ // When the number of ProbeEvents of mProbeEventsBuilder exceeds
+ // MAX_PROBE_EVENTS_COUNT, stop adding ProbeEvent.
+ // TODO: consider recording the total number of probes in a separate field to know how
+ // many probes are skipped.
+ if (mProbeEventsBuilder.getProbeEventCount() >= MAX_PROBE_EVENTS_COUNT) return;
+
+ int latencyUs = NetworkStackUtils.saturatedCast(durationUs);
+
+ final ProbeEvent.Builder probeEventBuilder = ProbeEvent.newBuilder()
+ .setLatencyMicros(latencyUs)
+ .setProbeType(type)
+ .setProbeResult(result);
+
+ if (capportData != null) {
+ final long secondsRemaining =
+ (capportData.getExpiryTimeMillis() - currentTimeMillis()) / 1000;
+ mCapportApiDataBuilder
+ .setRemainingTtlSecs(NetworkStackUtils.saturatedCast(secondsRemaining))
+ // TODO: rename this field to setRemainingKBytes, or use a long
+ .setRemainingBytes(
+ NetworkStackUtils.saturatedCast(capportData.getByteLimit() / 1000))
+ .setHasPortalUrl((capportData.getUserPortalUrl() != null))
+ .setHasVenueInfo((capportData.getVenueInfoUrl() != null));
+ probeEventBuilder.setCapportApiData(mCapportApiDataBuilder);
+ }
+
+ mProbeEventsBuilder.addProbeEvent(probeEventBuilder);
+ }
+
+ /**
+ * Write the network validation info to mStatsBuilder.
+ */
+ public void setValidationResult(int result, String redirectUrl) {
+ mStatsBuilder.setValidationResult(validationResultToEnum(result, redirectUrl));
+ }
+
+ /**
+ * Write the NetworkValidationReported proto to statsd.
+ *
+ * <p>This is a no-op if {@link #startCollection(NetworkCapabilities)} was not called since the
+ * last call to this method.
+ */
+ public NetworkValidationReported maybeStopCollectionAndSend() {
+ if (!mWatch.isStarted()) return null;
+ mStatsBuilder.setProbeEvents(mProbeEventsBuilder);
+ mStatsBuilder.setLatencyMicros(NetworkStackUtils.saturatedCast(mWatch.stop()));
+ mStatsBuilder.setValidationIndex(mValidationIndex);
+ // write a random value(0 ~ 999) for sampling.
+ mStatsBuilder.setRandomNumber((int) (Math.random() * 1000));
+ final NetworkValidationReported stats = mStatsBuilder.build();
+ final byte[] probeEvents = stats.getProbeEvents().toByteArray();
+
+ NetworkStackStatsLog.write(NetworkStackStatsLog.NETWORK_VALIDATION_REPORTED,
+ stats.getTransportType().getNumber(),
+ probeEvents,
+ stats.getValidationResult().getNumber(),
+ stats.getLatencyMicros(),
+ stats.getValidationIndex(),
+ stats.getRandomNumber());
+ mWatch.reset();
+ return stats;
+ }
+}
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto
new file mode 100644
index 0000000..8a94db9
--- /dev/null
+++ b/src/com/android/networkstack/metrics/stats.proto
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package com.android.networkstack.metrics;
+
+import "frameworks/base/core/proto/android/stats/connectivity/network_stack.proto";
+
+message CapportApiData {
+ // The TTL of the network connection provided by captive portal
+ optional int32 remaining_ttl_secs = 1;
+
+ // The limit traffic data of the network connection provided by captive portal
+ optional int32 remaining_bytes = 2;
+
+ // Is portal url option included in the DHCP packet (Yes, No)
+ optional bool has_portal_url = 3;
+
+ // Is venue info (e.g. store info, maps, flight status) included (Yes, No)
+ optional bool has_venue_info = 4;
+}
+
+message ProbeEvent {
+ // The probe type (http or https, or captive portal API...)
+ optional .android.stats.connectivity.ProbeType probe_type = 1;
+
+ // The latency in microseconds of the probe event
+ optional int32 latency_micros = 2;
+
+ // The result of the probe event
+ optional .android.stats.connectivity.ProbeResult probe_result = 3;
+
+ // The CaptivePortal API info
+ optional CapportApiData capport_api_data = 4;
+}
+
+message ProbeEvents {
+ // Record probe event during the validation
+ repeated ProbeEvent probe_event = 1;
+}
+
+/**
+ * The DHCP (Dynamic Host Configuration Protocol) session info
+ * Logged from:
+ * src/android/net/dhcp/DhcpClient.java
+ */
+message DhcpSession {
+ // The DHCP Feature(s) enabled in this session
+ repeated .android.stats.connectivity.DhcpFeature used_features = 1;
+
+ // The discover packet (re)transmit count
+ optional int32 discover_count = 2;
+
+ // The request packet (re)transmit count
+ optional int32 request_count = 3;
+
+ // The IPv4 address conflict count
+ // (only be meaningful when duplicate address detection is enabled)
+ optional int32 conflict_count = 4;
+
+ // The DHCP packet parsing error code in this session
+ // (defined in android.net.metrics.DhcpErrorEvent)
+ repeated .android.stats.connectivity.DhcpErrorCode error_code = 5;
+
+ // The result of DHCP hostname transliteration
+ optional .android.stats.connectivity.HostnameTransResult ht_result = 6;
+}
+
+/**
+ * Logs Network IP provisioning event
+ * Logged from:
+ * src/com/android/networkstack/metrics/NetworkIpProvisioningMetrics.java
+ */
+message NetworkIpProvisioningReported {
+ // Transport type (WIFI, CELLULAR, BLUETOOTH, ..)
+ optional .android.stats.connectivity.TransportType transport_type = 1;
+
+ // The latency in microseconds of IP Provisioning over IPV4
+ optional int32 ipv4_latency_micros = 2;
+
+ // The latency in microseconds of IP Provisioning over IPV6
+ optional int32 ipv6_latency_micros = 3;
+
+ // The time duration between provisioning start and end (success or failure)
+ optional int64 provisioning_duration_micros = 4;
+
+ // The specific disconnect reason for this IP provisioning
+ optional .android.stats.connectivity.DisconnectCode disconnect_code = 5;
+
+ // Log DHCP session info (Only valid for IPv4)
+ optional DhcpSession dhcp_session = 6;
+
+ // The random number between 0 ~ 999 for sampling
+ optional int32 random_number = 7;
+}
+
+/**
+ * Logs Network DHCP Renew event
+ * Logged from:
+ * src/android/net/dhcp/DhcpClient.java
+ */
+message NetworkDhcpRenewReported {
+ // Transport type (WIFI, CELLULAR, BLUETOOTH, ..)
+ optional .android.stats.connectivity.TransportType transport_type = 1;
+
+ // The request packet (re)transmit count
+ optional int32 request_count = 2;
+
+ // The latency in microseconds of DHCP Renew
+ optional int32 latency_micros = 3;
+
+ // The DHCP error code is defined in android.net.metrics.DhcpErrorEvent
+ optional .android.stats.connectivity.DhcpErrorCode error_code = 4;
+
+ // The result of DHCP renew
+ optional .android.stats.connectivity.DhcpRenewResult renew_result = 5;
+
+ // The random number between 0 ~ 999 for sampling
+ optional int32 random_number = 6;
+}
+
+/**
+ * Logs Network Validation event
+ * Logged from:
+ * src/com/android/server/connectivity/NetworkMonitor.java
+ */
+message NetworkValidationReported {
+ // Transport type (WIFI, CELLULAR, BLUETOOTH, ..)
+ optional .android.stats.connectivity.TransportType transport_type = 1;
+
+ // Record each probe event
+ optional ProbeEvents probe_events = 2;
+
+ // The result of the network validation
+ optional .android.stats.connectivity.ValidationResult validation_result = 3;
+
+ // The latency in microseconds of network validation
+ optional int32 latency_micros = 4;
+
+ // The validation index (the first validation attempt or second, third...)
+ optional int32 validation_index = 5;
+
+ // The random number between 0 ~ 999 for sampling
+ optional int32 random_number = 6;
+}
+
+/**
+ * Logs NetworkStack Quirk event
+ * Logged from:
+ * src/com/android/networkstack/
+ * This will be defined as count metrics on server side
+ */
+message NetworkStackQuirkReported {
+ // Transport type (WIFI, CELLULAR, BLUETOOTH, ..)
+ optional .android.stats.connectivity.TransportType transport_type = 1;
+
+ // Record each Quirk event
+ optional .android.stats.connectivity.NetworkQuirkEvent event = 2;
+}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 8a1b0c7..40de26e 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -83,6 +83,7 @@
import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_DNS_EVENTS;
import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_TCP_METRICS;
+import static com.android.networkstack.apishim.ConstantsShim.TRANSPORT_TEST;
import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG;
@@ -128,6 +129,8 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.stats.connectivity.ProbeResult;
+import android.stats.connectivity.ProbeType;
import android.telephony.AccessNetworkConstants;
import android.telephony.CellIdentityNr;
import android.telephony.CellInfo;
@@ -154,6 +157,7 @@
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.RingBufferIndices;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -167,6 +171,7 @@
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.metrics.DataStallDetectionStats;
import com.android.networkstack.metrics.DataStallStatsUtils;
+import com.android.networkstack.metrics.NetworkValidationMetrics;
import com.android.networkstack.netlink.TcpSocketTracker;
import com.android.networkstack.util.DnsUtils;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -387,7 +392,8 @@
private static final int CMD_BANDWIDTH_CHECK_TIMEOUT = 24;
// Start mReevaluateDelayMs at this value and double.
- private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
+ @VisibleForTesting
+ static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
// Default timeout of evaluating network bandwidth.
private static final int DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS = 10_000;
@@ -444,7 +450,14 @@
protected boolean mIsCaptivePortalCheckEnabled;
private boolean mUseHttps;
- // The total number of captive portal detection attempts for this NetworkMonitor instance.
+ /**
+ * The total number of completed validation attempts (network validated or a captive portal was
+ * detected) for this NetworkMonitor instance.
+ * This does not include attempts that were interrupted, retried or finished with a result that
+ * is not success or portal. See {@code mValidationIndex} in {@link NetworkValidationMetrics}
+ * for a count of all attempts.
+ * TODO: remove when removing legacy metrics.
+ */
private int mValidations = 0;
// Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
@@ -498,6 +511,18 @@
private final boolean mPrivateIpNoInternetEnabled;
+ private final boolean mMetricsEnabled;
+
+ // The validation metrics are accessed by individual probe threads, and by the StateMachine
+ // thread. All accesses must be synchronized to make sure the StateMachine thread can see
+ // reports from all probes.
+ // TODO: as that most usage is in the StateMachine thread and probes only add their probe
+ // events, consider having probes return their stats to the StateMachine, and only access this
+ // member on the StateMachine thread without synchronization.
+ @GuardedBy("mNetworkValidationMetrics")
+ private final NetworkValidationMetrics mNetworkValidationMetrics =
+ new NetworkValidationMetrics();
+
private int getCallbackVersion(INetworkMonitorCallbacks cb) {
int version;
try {
@@ -561,6 +586,8 @@
mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
mPrivateIpNoInternetEnabled = getIsPrivateIpNoInternetEnabled();
+ mMetricsEnabled = deps.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
+ NetworkStackUtils.VALIDATION_METRICS_VERSION, true /* defaultEnabled */);
mUseHttps = getUseHttpsValidation();
mCaptivePortalUserAgent = getCaptivePortalUserAgent();
mCaptivePortalHttpsUrls = makeCaptivePortalHttpsUrls();
@@ -773,6 +800,51 @@
}
}
+ private void startMetricsCollection() {
+ if (!mMetricsEnabled) return;
+ try {
+ synchronized (mNetworkValidationMetrics) {
+ mNetworkValidationMetrics.startCollection(mNetworkCapabilities);
+ }
+ } catch (Exception e) {
+ Log.wtf(TAG, "Error resetting validation metrics", e);
+ }
+ }
+
+ private void recordProbeEventMetrics(ProbeType type, long latencyMicros, ProbeResult result,
+ CaptivePortalDataShim capportData) {
+ if (!mMetricsEnabled) return;
+ try {
+ synchronized (mNetworkValidationMetrics) {
+ mNetworkValidationMetrics.addProbeEvent(type, latencyMicros, result, capportData);
+ }
+ } catch (Exception e) {
+ Log.wtf(TAG, "Error recording probe event", e);
+ }
+ }
+
+ private void recordValidationResult(int result, String redirectUrl) {
+ if (!mMetricsEnabled) return;
+ try {
+ synchronized (mNetworkValidationMetrics) {
+ mNetworkValidationMetrics.setValidationResult(result, redirectUrl);
+ }
+ } catch (Exception e) {
+ Log.wtf(TAG, "Error recording validation result", e);
+ }
+ }
+
+ private void maybeStopCollectionAndSendMetrics() {
+ if (!mMetricsEnabled) return;
+ try {
+ synchronized (mNetworkValidationMetrics) {
+ mNetworkValidationMetrics.maybeStopCollectionAndSend();
+ }
+ } catch (Exception e) {
+ Log.wtf(TAG, "Error sending validation stats", e);
+ }
+ }
+
// DefaultState is the parent of all States. It exists only to handle CMD_* messages but
// does not entail any real state (hence no enter() or exit() routines).
private class DefaultState extends State {
@@ -785,11 +857,17 @@
transitionTo(mEvaluatingState);
return HANDLED;
case CMD_NETWORK_DISCONNECTED:
+ maybeStopCollectionAndSendMetrics();
logNetworkEvent(NetworkEvent.NETWORK_DISCONNECTED);
quit();
return HANDLED;
case CMD_FORCE_REEVALUATION:
case CMD_CAPTIVE_PORTAL_RECHECK:
+ if (getCurrentState() == mDefaultState) {
+ // Before receiving CMD_NETWORK_CONNECTED (when still in mDefaultState),
+ // requests to reevaluate are not valid: drop them.
+ return HANDLED;
+ }
String msg = "Forcing reevaluation for UID " + message.arg1;
final DnsStallDetector dsd = getDnsStallDetector();
if (dsd != null) {
@@ -931,6 +1009,7 @@
initSocketTrackingIfRequired();
// start periodical polling.
sendTcpPollingEvent();
+ maybeStopCollectionAndSendMetrics();
}
private void initSocketTrackingIfRequired() {
@@ -950,6 +1029,9 @@
transitionTo(mValidatedState);
break;
case CMD_EVALUATE_PRIVATE_DNS:
+ // TODO: this causes reevaluation of a single probe that is not counted in
+ // metrics. Add support for such reevaluation probes in metrics, and log them
+ // separately.
transitionTo(mEvaluatingPrivateDnsState);
break;
case EVENT_DNS_NOTIFICATION:
@@ -1090,7 +1172,7 @@
final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
// Use redirect URL from AP if exists.
final String portalUrl =
- (useRedirectUrlForPortal() && probeRes.redirectUrl != null)
+ (useRedirectUrlForPortal() && makeURL(probeRes.redirectUrl) != null)
? probeRes.redirectUrl : probeRes.detectUrl;
appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, portalUrl);
if (probeRes.probeSpec != null) {
@@ -1278,6 +1360,7 @@
sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
mValidations++;
+ maybeStopCollectionAndSendMetrics();
}
@Override
@@ -1309,6 +1392,12 @@
notifyPrivateDnsConfigResolved();
} else {
handlePrivateDnsEvaluationFailure();
+ // The private DNS probe fails-fast if the server hostname cannot
+ // be resolved. Record it as a failure with zero latency.
+ // TODO: refactor this together with the probe recorded in
+ // sendPrivateDnsProbe, so logging is symmetric / easier to follow.
+ recordProbeEventMetrics(ProbeType.PT_PRIVDNS, 0 /* latency */,
+ ProbeResult.PR_FAILURE, null /* capportData */);
break;
}
}
@@ -1418,6 +1507,8 @@
validationLog(PROBE_PRIVDNS, host,
String.format("%dus - Error: %s", time, uhe.getMessage()));
}
+ recordProbeEventMetrics(ProbeType.PT_PRIVDNS, time, success ? ProbeResult.PR_SUCCESS :
+ ProbeResult.PR_FAILURE, null /* capportData */);
logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
return success;
}
@@ -1428,14 +1519,23 @@
@Override
public void enter() {
+ // When starting a full probe cycle here, record any pending stats (for example if
+ // CMD_FORCE_REEVALUATE was called before evaluation finished, as can happen in
+ // EvaluatingPrivateDnsState).
+ maybeStopCollectionAndSendMetrics();
+ // Restart the metrics collection timers. Metrics will be stopped and sent when the
+ // validation attempt finishes (as success, failure or portal), or if it is interrupted
+ // (by being restarted or if NetworkMonitor stops).
+ startMetricsCollection();
if (mEvaluateAttempts >= BLAME_FOR_EVALUATION_ATTEMPTS) {
//Don't continue to blame UID forever.
TrafficStats.clearThreadStatsUid();
}
final int token = ++mProbeToken;
+ final EvaluationThreadDeps deps = new EvaluationThreadDeps(mNetworkCapabilities);
mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
- isCaptivePortal())));
+ isCaptivePortal(deps))));
mThread.start();
}
@@ -1508,6 +1608,9 @@
private class WaitingForNextProbeState extends State {
@Override
public void enter() {
+ // Send metrics for this evaluation attempt. Metrics collection (and its timers) will be
+ // restarted when the next probe starts.
+ maybeStopCollectionAndSendMetrics();
scheduleNextProbe();
}
@@ -1728,6 +1831,7 @@
}
try {
final List<CellInfo> cells = mTelephonyManager.getAllCellInfo();
+ if (cells == null) return null;
final Map<String, Integer> countryCodeMap = new HashMap<>();
int maxCount = 0;
for (final CellInfo cell : cells) {
@@ -2147,8 +2251,25 @@
return mCaptivePortalFallbackSpecs[idx];
}
- @VisibleForTesting
- protected CaptivePortalProbeResult isCaptivePortal() {
+ /**
+ * Parameters that can be accessed by the evaluation thread in a thread-safe way.
+ *
+ * Parameters such as LinkProperties and NetworkCapabilities cannot be accessed by the
+ * evaluation thread directly, as they are managed in the state machine thread and not
+ * synchronized. This class provides a copy of the required data that is not modified and can be
+ * used safely by the evaluation thread.
+ */
+ private static class EvaluationThreadDeps {
+ // TODO: add parameters that are accessed in a non-thread-safe way from the evaluation
+ // thread (read from LinkProperties, NetworkCapabilities, useHttps, validationStage)
+ private final boolean mIsTestNetwork;
+
+ EvaluationThreadDeps(NetworkCapabilities nc) {
+ this.mIsTestNetwork = nc.hasTransport(TRANSPORT_TEST);
+ }
+ }
+
+ private CaptivePortalProbeResult isCaptivePortal(EvaluationThreadDeps deps) {
if (!mIsCaptivePortalCheckEnabled) {
validationLog("Validation disabled.");
return CaptivePortalProbeResult.success(CaptivePortalProbeResult.PROBE_UNKNOWN);
@@ -2196,11 +2317,11 @@
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
} else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) {
// Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes.
- result = sendHttpAndHttpsParallelWithFallbackProbes(
- proxyInfo, httpsUrls[0], httpUrls[0]);
+ result = sendHttpAndHttpsParallelWithFallbackProbes(deps, proxyInfo,
+ httpsUrls[0], httpUrls[0]);
} else if (mUseHttps) {
// Support result aggregation from multiple Urls.
- result = sendMultiParallelHttpAndHttpsProbes(proxyInfo, httpsUrls, httpUrls);
+ result = sendMultiParallelHttpAndHttpsProbes(deps, proxyInfo, httpsUrls, httpUrls);
} else {
result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP);
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
@@ -2239,6 +2360,8 @@
// network validation (the HTTPS probe, which would likely fail anyway) or the PAC probe.
if (mPrivateIpNoInternetEnabled && probeType == ValidationProbeEvent.PROBE_HTTP
&& (proxy == null) && hasPrivateIpAddress(resolvedAddr)) {
+ recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType),
+ 0 /* latency */, ProbeResult.PR_PRIVATE_IP_DNS, null /* capportData */);
return CaptivePortalProbeResult.PRIVATE_IP;
}
return sendHttpProbe(url, probeType, null);
@@ -2270,6 +2393,9 @@
result = ValidationProbeEvent.DNS_FAILURE;
}
final long latency = watch.stop();
+ recordProbeEventMetrics(ProbeType.PT_DNS, latency,
+ (result == ValidationProbeEvent.DNS_SUCCESS) ? ProbeResult.PR_SUCCESS :
+ ProbeResult.PR_FAILURE, null /* capportData */);
logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
return addresses;
}
@@ -2387,12 +2513,17 @@
}
logValidationProbe(probeTimer.stop(), probeType, httpResponseCode);
+ final CaptivePortalProbeResult probeResult;
if (probeSpec == null) {
- return new CaptivePortalProbeResult(httpResponseCode, redirectUrl, url.toString(),
- 1 << probeType);
+ probeResult = new CaptivePortalProbeResult(httpResponseCode, redirectUrl,
+ url.toString(), 1 << probeType);
} else {
- return probeSpec.getResult(httpResponseCode, redirectUrl);
+ probeResult = probeSpec.getResult(httpResponseCode, redirectUrl);
}
+ recordProbeEventMetrics(NetworkValidationMetrics.probeTypeToEnum(probeType),
+ probeTimer.stop(), NetworkValidationMetrics.httpProbeResultToEnum(probeResult),
+ null /* capportData */);
+ return probeResult;
}
@VisibleForTesting
@@ -2482,12 +2613,12 @@
private final CountDownLatch mLatch;
private final Probe mProbe;
- ProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, int probeType,
- Uri captivePortalApiUrl) {
+ ProbeThread(CountDownLatch latch, EvaluationThreadDeps deps, ProxyInfo proxy, URL url,
+ int probeType, Uri captivePortalApiUrl) {
mLatch = latch;
mProbe = (probeType == ValidationProbeEvent.PROBE_HTTPS)
- ? new HttpsProbe(proxy, url, captivePortalApiUrl)
- : new HttpProbe(proxy, url, captivePortalApiUrl);
+ ? new HttpsProbe(deps, proxy, url, captivePortalApiUrl)
+ : new HttpProbe(deps, proxy, url, captivePortalApiUrl);
mResult = CaptivePortalProbeResult.failed(probeType);
}
@@ -2512,11 +2643,14 @@
}
private abstract static class Probe {
+ protected final EvaluationThreadDeps mDeps;
protected final ProxyInfo mProxy;
protected final URL mUrl;
protected final Uri mCaptivePortalApiUrl;
- protected Probe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+ protected Probe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url,
+ Uri captivePortalApiUrl) {
+ mDeps = deps;
mProxy = proxy;
mUrl = url;
mCaptivePortalApiUrl = captivePortalApiUrl;
@@ -2526,8 +2660,8 @@
}
final class HttpsProbe extends Probe {
- HttpsProbe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
- super(proxy, url, captivePortalApiUrl);
+ HttpsProbe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+ super(deps, proxy, url, captivePortalApiUrl);
}
@Override
@@ -2537,18 +2671,24 @@
}
final class HttpProbe extends Probe {
- HttpProbe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
- super(proxy, url, captivePortalApiUrl);
+ HttpProbe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+ super(deps, proxy, url, captivePortalApiUrl);
}
- private CaptivePortalDataShim tryCapportApiProbe() {
- if (mCaptivePortalApiUrl == null) return null;
+ private CaptivePortalDataShim sendCapportApiProbe() {
+ // TODO: consider adding metrics counters for each case returning null in this method
+ // (cases where the API is not implemented properly).
validationLog("Fetching captive portal data from " + mCaptivePortalApiUrl);
final String apiContent;
try {
final URL url = new URL(mCaptivePortalApiUrl.toString());
- if (!"https".equals(url.getProtocol())) {
+ // Protocol must be HTTPS
+ // (as per https://www.ietf.org/id/draft-ietf-capport-api-07.txt, #4).
+ // Only allow HTTP on localhost, for testing.
+ final boolean isTestLocalhostHttp = mDeps.mIsTestNetwork
+ && "localhost".equals(url.getHost()) && "http".equals(url.getProtocol());
+ if (!"https".equals(url.getProtocol()) && !isTestLocalhostHttp) {
validationLog("Invalid captive portal API protocol: " + url.getProtocol());
return null;
}
@@ -2576,26 +2716,38 @@
try {
final JSONObject info = new JSONObject(apiContent);
- return CaptivePortalDataShimImpl.fromJson(info);
+ final CaptivePortalDataShim capportData = CaptivePortalDataShimImpl.fromJson(info);
+ if (capportData != null && capportData.isCaptive()
+ && capportData.getUserPortalUrl() == null) {
+ validationLog("Missing user-portal-url from capport response");
+ return null;
+ }
+ return capportData;
} catch (JSONException e) {
validationLog("Could not parse capport API JSON: " + e.getMessage());
return null;
} catch (UnsupportedApiLevelException e) {
+ // This should never happen because LinkProperties would not have a capport URL
+ // before R.
validationLog("Platform API too low to support capport API");
return null;
}
}
+ private CaptivePortalDataShim tryCapportApiProbe() {
+ if (mCaptivePortalApiUrl == null) return null;
+ final Stopwatch capportApiWatch = new Stopwatch().start();
+ final CaptivePortalDataShim capportData = sendCapportApiProbe();
+ recordProbeEventMetrics(ProbeType.PT_CAPPORT_API, capportApiWatch.stop(),
+ capportData == null ? ProbeResult.PR_FAILURE : ProbeResult.PR_SUCCESS,
+ capportData);
+ return capportData;
+ }
+
@Override
protected CaptivePortalProbeResult sendProbe() {
final CaptivePortalDataShim capportData = tryCapportApiProbe();
if (capportData != null && capportData.isCaptive()) {
- if (capportData.getUserPortalUrl() == null) {
- validationLog("Missing user-portal-url from capport response");
- return new CapportApiProbeResult(
- sendDnsAndHttpProbes(mProxy, mUrl, ValidationProbeEvent.PROBE_HTTP),
- capportData);
- }
final String loginUrlString = capportData.getUserPortalUrl().toString();
// Starting from R (where CaptivePortalData was introduced), the captive portal app
// delegates to NetworkMonitor for verifying when the network validates instead of
@@ -2631,8 +2783,9 @@
&& captivePortalApiUrl == null);
}
- private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(@NonNull ProxyInfo proxy,
- @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls) {
+ private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(
+ @NonNull EvaluationThreadDeps deps, @Nullable ProxyInfo proxy, @NonNull URL[] httpsUrls,
+ @NonNull URL[] httpUrls) {
// If multiple URLs are required to ensure the correctness of validation, send parallel
// probes to explore the result in separate probe threads and aggregate those results into
// one as the final result for either HTTP or HTTPS.
@@ -2657,13 +2810,13 @@
// TODO: Have the capport probe as a different probe for cleanliness.
final URL urlMaybeWithCapport = httpUrls[0];
for (final URL url : httpUrls) {
- futures.add(ecs.submit(() -> new HttpProbe(proxy, url,
+ futures.add(ecs.submit(() -> new HttpProbe(deps, proxy, url,
url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe()));
}
for (final URL url : httpsUrls) {
- futures.add(
- ecs.submit(() -> new HttpsProbe(proxy, url, capportApiUrl).sendProbe()));
+ futures.add(ecs.submit(() -> new HttpsProbe(deps, proxy, url, capportApiUrl)
+ .sendProbe()));
}
final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>();
@@ -2759,15 +2912,15 @@
}
private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes(
- ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
+ EvaluationThreadDeps deps, ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
// Number of probes to wait for. If a probe completes with a conclusive answer
// it shortcuts the latch immediately by forcing the count to 0.
final CountDownLatch latch = new CountDownLatch(2);
final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties);
- final ProbeThread httpsProbe = new ProbeThread(latch, proxy, httpsUrl,
+ final ProbeThread httpsProbe = new ProbeThread(latch, deps, proxy, httpsUrl,
ValidationProbeEvent.PROBE_HTTPS, capportApiUrl);
- final ProbeThread httpProbe = new ProbeThread(latch, proxy, httpUrl,
+ final ProbeThread httpProbe = new ProbeThread(latch, deps, proxy, httpUrl,
ValidationProbeEvent.PROBE_HTTP, capportApiUrl);
try {
@@ -3308,6 +3461,7 @@
p.redirectUrl = redirectUrl;
p.timestampMillis = SystemClock.elapsedRealtime();
notifyNetworkTested(p);
+ recordValidationResult(result, redirectUrl);
}
@VisibleForTesting
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index be338e5..9b1be06 100644
--- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -16,8 +16,8 @@
package com.android.server.connectivity.ipmemorystore;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import android.content.ContentValues;
import android.content.Context;
@@ -335,21 +335,19 @@
// Returns the expiry date of the specified row, or one of the error codes above if the
// row is not found or some other error
static long getExpiry(@NonNull final SQLiteDatabase db, @NonNull final String key) {
- final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+ try (Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
EXPIRY_COLUMN, // columns
SELECT_L2KEY, // selection
new String[] { key }, // selectionArgs
null, // groupBy
null, // having
- null // orderBy
- );
- // L2KEY is the primary key ; it should not be possible to get more than one
- // result here. 0 results means the key was not found.
- if (cursor.getCount() != 1) return EXPIRY_ERROR;
- cursor.moveToFirst();
- final long result = cursor.getLong(0); // index in the EXPIRY_COLUMN array
- cursor.close();
- return result;
+ null)) { // orderBy
+ // L2KEY is the primary key ; it should not be possible to get more than one
+ // result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return EXPIRY_ERROR;
+ cursor.moveToFirst();
+ return cursor.getLong(0); // index in the EXPIRY_COLUMN array
+ }
}
static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
@@ -399,20 +397,19 @@
@Nullable
static NetworkAttributes retrieveNetworkAttributes(@NonNull final SQLiteDatabase db,
@NonNull final String key) {
- final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+ try (Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
null, // columns, null means everything
NetworkAttributesContract.COLNAME_L2KEY + " = ?", // selection
new String[] { key }, // selectionArgs
null, // groupBy
null, // having
- null); // orderBy
- // L2KEY is the primary key ; it should not be possible to get more than one
- // result here. 0 results means the key was not found.
- if (cursor.getCount() != 1) return null;
- cursor.moveToFirst();
- final NetworkAttributes attributes = readNetworkAttributesLine(cursor);
- cursor.close();
- return attributes;
+ null)) { // orderBy
+ // L2KEY is the primary key ; it should not be possible to get more than one
+ // result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return null;
+ cursor.moveToFirst();
+ return readNetworkAttributesLine(cursor);
+ }
}
private static final String[] DATA_COLUMN = new String[] {
@@ -422,7 +419,7 @@
@Nullable
static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
@NonNull final String clientId, @NonNull final String name) {
- final Cursor cursor = db.query(PrivateDataContract.TABLENAME,
+ try (Cursor cursor = db.query(PrivateDataContract.TABLENAME,
DATA_COLUMN, // columns
PrivateDataContract.COLNAME_L2KEY + " = ? AND " // selection
+ PrivateDataContract.COLNAME_CLIENT + " = ? AND "
@@ -430,14 +427,13 @@
new String[] { key, clientId, name }, // selectionArgs
null, // groupBy
null, // having
- null); // orderBy
- // The query above is querying by (composite) primary key, so it should not be possible to
- // get more than one result here. 0 results means the key was not found.
- if (cursor.getCount() != 1) return null;
- cursor.moveToFirst();
- final byte[] result = cursor.getBlob(0); // index in the DATA_COLUMN array
- cursor.close();
- return result;
+ null)) { // orderBy
+ // The query above is querying by (composite) primary key, so it should not be possible
+ // to get more than one result here. 0 results means the key was not found.
+ if (cursor.getCount() != 1) return null;
+ cursor.moveToFirst();
+ return cursor.getBlob(0); // index in the DATA_COLUMN array
+ }
}
/**
@@ -449,7 +445,7 @@
try {
db.delete(NetworkAttributesContract.TABLENAME, null, null);
db.delete(PrivateDataContract.TABLENAME, null, null);
- final Cursor cursorNetworkAttributes = db.query(
+ try (Cursor cursorNetworkAttributes = db.query(
// table name
NetworkAttributesContract.TABLENAME,
// column name
@@ -459,13 +455,10 @@
null, // groupBy
null, // having
null, // orderBy
- "1"); // limit
- if (0 != cursorNetworkAttributes.getCount()) {
- cursorNetworkAttributes.close();
- continue;
+ "1")) { // limit
+ if (0 != cursorNetworkAttributes.getCount()) continue;
}
- cursorNetworkAttributes.close();
- final Cursor cursorPrivateData = db.query(
+ try (Cursor cursorPrivateData = db.query(
// table name
PrivateDataContract.TABLENAME,
// column name
@@ -475,14 +468,10 @@
null, // groupBy
null, // having
null, // orderBy
- "1"); // limit
- if (0 != cursorPrivateData.getCount()) {
- cursorPrivateData.close();
- continue;
+ "1")) { // limit
+ if (0 != cursorPrivateData.getCount()) continue;
}
- cursorPrivateData.close();
db.setTransactionSuccessful();
- return;
} catch (SQLiteException e) {
Log.e(TAG, "Could not wipe the data in database", e);
} finally {
@@ -575,7 +564,7 @@
final String selection = NetworkAttributesContract.COLNAME_EXPIRYDATE + " > ? AND ("
+ sj.toString() + ")";
- final Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
+ try (Cursor cursor = db.queryWithFactory(new CustomCursorFactory(args),
false, // distinct
NetworkAttributesContract.TABLENAME,
null, // columns, null means everything
@@ -584,22 +573,81 @@
null, // groupBy
null, // having
null, // orderBy
- null); // limit
- if (cursor.getCount() <= 0) return null;
- cursor.moveToFirst();
- String bestKey = null;
- float bestMatchConfidence = GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
- while (!cursor.isAfterLast()) {
- final NetworkAttributes read = readNetworkAttributesLine(cursor);
- final float confidence = read.getNetworkGroupSamenessConfidence(attr);
- if (confidence > bestMatchConfidence) {
- bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
- bestMatchConfidence = confidence;
+ null)) { // limit
+ if (cursor.getCount() <= 0) return null;
+ cursor.moveToFirst();
+ String bestKey = null;
+ float bestMatchConfidence =
+ GROUPCLOSE_CONFIDENCE; // Never return a match worse than this.
+ while (!cursor.isAfterLast()) {
+ final NetworkAttributes read = readNetworkAttributesLine(cursor);
+ final float confidence = read.getNetworkGroupSamenessConfidence(attr);
+ if (confidence > bestMatchConfidence) {
+ bestKey = getString(cursor, NetworkAttributesContract.COLNAME_L2KEY);
+ bestMatchConfidence = confidence;
+ }
+ cursor.moveToNext();
}
- cursor.moveToNext();
+ return bestKey;
}
- cursor.close();
- return bestKey;
+ }
+
+ /**
+ * Delete a single entry by key.
+ *
+ * If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will
+ * only be marked deleted, and overwritten by subsequent writes or reclaimed during the next
+ * maintenance window.
+ * Note that wiping data is a very expensive operation. This is meant for clients that need
+ * this data gone from disk immediately for security reasons. Functionally it makes no
+ * difference at all.
+ */
+ static StatusAndCount delete(@NonNull final SQLiteDatabase db, @NonNull final String l2key,
+ final boolean needWipe) {
+ return deleteEntriesWithColumn(db,
+ NetworkAttributesContract.COLNAME_L2KEY, l2key, needWipe);
+ }
+
+ /**
+ * Delete all entries that have a particular cluster value.
+ *
+ * If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will
+ * only be marked deleted, and overwritten by subsequent writes or reclaimed during the next
+ * maintenance window.
+ * Note that wiping data is a very expensive operation. This is meant for clients that need
+ * this data gone from disk immediately for security reasons. Functionally it makes no
+ * difference at all.
+ */
+ static StatusAndCount deleteCluster(@NonNull final SQLiteDatabase db,
+ @NonNull final String cluster, final boolean needWipe) {
+ return deleteEntriesWithColumn(db,
+ NetworkAttributesContract.COLNAME_CLUSTER, cluster, needWipe);
+ }
+
+ // Delete all entries where the given column has the given value.
+ private static StatusAndCount deleteEntriesWithColumn(@NonNull final SQLiteDatabase db,
+ @NonNull final String column, @NonNull final String value, final boolean needWipe) {
+ db.beginTransaction();
+ int deleted = 0;
+ try {
+ deleted = db.delete(NetworkAttributesContract.TABLENAME,
+ column + "= ?", new String[] { value });
+ db.setTransactionSuccessful();
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Could not delete from the memory store", e);
+ // Unclear what might have happened ; deleting records is not supposed to be able
+ // to fail barring a syntax error in the SQL query.
+ return new StatusAndCount(Status.ERROR_UNKNOWN, 0);
+ } finally {
+ db.endTransaction();
+ }
+
+ if (needWipe) {
+ final int vacuumStatus = vacuum(db);
+ // This is a problem for the client : return the failure
+ if (Status.SUCCESS != vacuumStatus) return new StatusAndCount(vacuumStatus, deleted);
+ }
+ return new StatusAndCount(Status.SUCCESS, deleted);
}
// Drops all records that are expired. Relevance has decayed to zero of these records. Returns
@@ -637,20 +685,21 @@
}
// Queries number of NetworkAttributes that start from the lowest expiryDate.
- final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+ final long expiryDate;
+ try (Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
new String[] {NetworkAttributesContract.COLNAME_EXPIRYDATE}, // columns
null, // selection
null, // selectionArgs
null, // groupBy
null, // having
NetworkAttributesContract.COLNAME_EXPIRYDATE, // orderBy
- Integer.toString(number)); // limit
- if (cursor == null || cursor.getCount() <= 0) return Status.ERROR_GENERIC;
- cursor.moveToLast();
+ Integer.toString(number))) { // limit
+ if (cursor == null || cursor.getCount() <= 0) return Status.ERROR_GENERIC;
+ cursor.moveToLast();
- //Get the expiryDate from last record.
- final long expiryDate = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, 0);
- cursor.close();
+ // Get the expiryDate from last record.
+ expiryDate = getLong(cursor, NetworkAttributesContract.COLNAME_EXPIRYDATE, 0);
+ }
db.beginTransaction();
try {
@@ -678,15 +727,16 @@
static int getTotalRecordNumber(@NonNull final SQLiteDatabase db) {
// Query the total number of NetworkAttributes
- final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+ try (Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
new String[] {"COUNT(*)"}, // columns
null, // selection
null, // selectionArgs
null, // groupBy
null, // having
- null); // orderBy
- cursor.moveToFirst();
- return cursor == null ? 0 : cursor.getInt(0);
+ null)) { // orderBy
+ cursor.moveToFirst();
+ return cursor == null ? 0 : cursor.getInt(0);
+ }
}
// Helper methods
@@ -708,4 +758,13 @@
final int columnIndex = cursor.getColumnIndex(columnName);
return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
}
+ private static int vacuum(@NonNull final SQLiteDatabase db) {
+ try {
+ db.execSQL("VACUUM");
+ return Status.SUCCESS;
+ } catch (SQLiteException e) {
+ // Vacuuming may fail from lack of storage, because it makes a copy of the database.
+ return Status.ERROR_STORAGE;
+ }
+ }
}
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index cd29e0d..ae9c875 100644
--- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -33,6 +33,7 @@
import android.net.ipmemorystore.IOnL2KeyResponseListener;
import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusAndCountListener;
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
@@ -116,7 +117,11 @@
// TODO : investigate replacing this scheme with a scheme where each thread has its own
// instance of the database, as it may be faster. It is likely however that IpMemoryStore
// operations are mostly IO-bound anyway, and additional contention is unlikely to bring
- // benefits. Alternatively, a read-write lock might increase throughput.
+ // benefits. Alternatively, a read-write lock might increase throughput. Also if doing
+ // this work, care must be taken around the privacy-preserving VACUUM operations as
+ // VACUUM will fail if there are other open transactions at the same time, and using
+ // multiple threads will open the possibility of this failure happening, threatening
+ // the privacy guarantees.
mExecutor = Executors.newSingleThreadExecutor();
RegularMaintenanceJobService.schedule(mContext, this);
}
@@ -405,6 +410,56 @@
}
/**
+ * Delete a single entry.
+ *
+ * @param l2Key The L2 key of the entry to delete.
+ * @param needWipe Whether the data must be wiped from disk immediately for security reasons.
+ * This is very expensive and makes no functional difference ; only pass
+ * true if security requires this data must be removed from disk immediately.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * returns (through the listener) A status to indicate success and the number of deleted records
+ */
+ public void delete(@NonNull final String l2Key, final boolean needWipe,
+ @Nullable final IOnStatusAndCountListener listener) {
+ mExecutor.execute(() -> {
+ try {
+ final StatusAndCount res = IpMemoryStoreDatabase.delete(mDb, l2Key, needWipe);
+ if (null != listener) listener.onComplete(makeStatus(res.status), res.count);
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
+ }
+
+ /**
+ * Delete all entries in a cluster.
+ *
+ * This method will delete all entries in the memory store that have the cluster attribute
+ * passed as an argument.
+ *
+ * @param cluster The cluster to delete.
+ * @param needWipe Whether the data must be wiped from disk immediately for security reasons.
+ * This is very expensive and makes no functional difference ; only pass
+ * true if security requires this data must be removed from disk immediately.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * returns (through the listener) A status to indicate success and the number of deleted records
+ */
+ public void deleteCluster(@NonNull final String cluster, final boolean needWipe,
+ @Nullable final IOnStatusAndCountListener listener) {
+ mExecutor.execute(() -> {
+ try {
+ final StatusAndCount res =
+ IpMemoryStoreDatabase.deleteCluster(mDb, cluster, needWipe);
+ if (null != listener) listener.onComplete(makeStatus(res.status), res.count);
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
+ }
+
+ /**
* Wipe the data in IpMemoryStore database upon network factory reset.
*/
@Override
diff --git a/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java b/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java
new file mode 100644
index 0000000..2cbe843
--- /dev/null
+++ b/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java
@@ -0,0 +1,29 @@
+/*
+ * 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.connectivity.ipmemorystore;
+
+/**
+ * Small data class to wrap a Status and an int.
+ */
+public class StatusAndCount {
+ public final int status;
+ public final int count;
+ public StatusAndCount(final int status, final int count) {
+ this.status = status;
+ this.count = count;
+ }
+}
diff --git a/src/com/android/server/util/NetworkStackConstants.java b/src/com/android/server/util/NetworkStackConstants.java
index dbba7f3..6ecc84c 100644
--- a/src/com/android/server/util/NetworkStackConstants.java
+++ b/src/com/android/server/util/NetworkStackConstants.java
@@ -16,7 +16,7 @@
package com.android.server.util;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import java.net.Inet4Address;
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index e924d3a..af63f0e 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -82,6 +82,18 @@
test_suites: ["device-tests"],
}
+// The static lib needs to be jarjared by each module so they do not conflict with each other
+// (e.g. wifi, system server, network stack need to use different package names when including it).
+// Apply NetworkStack jarjar rules to the tests as well so classes in NetworkStaticLibTests have the
+// same package names as in module code.
+android_library {
+ name: "NetworkStackStaticLibTestsLib",
+ platform_apis: true,
+ min_sdk_version: "29",
+ jarjar_rules: ":NetworkStackJarJarRules",
+ static_libs: ["NetworkStaticLibTestsLib"],
+}
+
// Special version of the network stack tests that includes all tests necessary for code coverage
// purposes. This is currently the union of NetworkStackTests and NetworkStackIntegrationTests.
android_test {
@@ -92,7 +104,11 @@
test_suites: ["device-tests", "mts"],
test_config: "AndroidTest_Coverage.xml",
defaults: ["NetworkStackIntegrationTestsJniDefaults"],
- static_libs: ["NetworkStackTestsLib", "NetworkStackIntegrationTestsLib"],
+ static_libs: [
+ "NetworkStackTestsLib",
+ "NetworkStackIntegrationTestsLib",
+ "NetworkStackStaticLibTestsLib",
+ ],
compile_multilib: "both",
manifest: "AndroidManifest_coverage.xml",
}
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index 3e38a00..38eb84e 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -24,15 +24,15 @@
import static android.net.dhcp.DhcpPacket.ENCAP_L2;
import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
import static android.net.ipmemorystore.Status.SUCCESS;
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
import static android.system.OsConstants.ETH_P_IPV6;
import static android.system.OsConstants.IFA_F_TEMPORARY;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_TCP;
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
import static com.android.server.util.NetworkStackConstants.ARP_REPLY;
import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
@@ -136,6 +136,7 @@
import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.arp.ArpPacket;
+import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.server.NetworkObserver;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -171,6 +172,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Random;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -317,8 +319,8 @@
@Override
public DhcpClient.Dependencies getDhcpClientDependencies(
- NetworkStackIpMemoryStore ipMemoryStore) {
- return new DhcpClient.Dependencies(ipMemoryStore) {
+ NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) {
+ return new DhcpClient.Dependencies(ipMemoryStore, metrics) {
@Override
public boolean isFeatureEnabled(final Context context, final String name,
final boolean defaultEnabled) {
@@ -689,13 +691,12 @@
false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled,
isHostnameConfigurationEnabled, hostname, displayName, scanResultInfo);
return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu,
- isDhcpIpConflictDetectEnabled, captivePortalApiUrl);
+ captivePortalApiUrl);
}
private List<DhcpPacket> handleDhcpPackets(final boolean isSuccessLease,
final Integer leaseTimeSec, final boolean shouldReplyRapidCommitAck, final int mtu,
- final boolean isDhcpIpConflictDetectEnabled, final String captivePortalApiUrl)
- throws Exception {
+ final String captivePortalApiUrl) throws Exception {
final List<DhcpPacket> packetList = new ArrayList<>();
DhcpPacket packet;
while ((packet = getNextDhcpPacket()) != null) {
@@ -720,15 +721,6 @@
// wait for reply to DHCPOFFER packet if disabling rapid commit option
if (shouldReplyRapidCommitAck || !(packet instanceof DhcpDiscoverPacket)) {
- if (!isDhcpIpConflictDetectEnabled && isSuccessLease) {
- // verify IPv4-only provisioning success before exiting loop.
- // 1. if it's a failure lease, onProvisioningSuccess() won't be called;
- // 2. if duplicated IPv4 address detection is enabled, verify TIMEOUT
- // will affect ARP packet capture running in other test cases.
- ArgumentCaptor<LinkProperties> captor =
- ArgumentCaptor.forClass(LinkProperties.class);
- verifyProvisioningSuccess(captor, Collections.singletonList(CLIENT_ADDR));
- }
return packetList;
}
}
@@ -790,8 +782,15 @@
verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(emptyLp);
}
- private void verifyProvisioningSuccess(ArgumentCaptor<LinkProperties> captor,
- final Collection<InetAddress> addresses) throws Exception {
+ // Verify IPv4-only provisioning success. No need to verify IPv4 provisioning when below cases
+ // happen:
+ // 1. if there's a failure lease, onProvisioningSuccess() won't be called;
+ // 2. if duplicated IPv4 address detection is enabled, verify TIMEOUT will affect ARP packets
+ // capture running in other test cases.
+ // 3. if IPv6 is enabled, e.g. withoutIPv6() isn't called when starting provisioning.
+ private void verifyIPv4OnlyProvisioningSuccess(final Collection<InetAddress> addresses)
+ throws Exception {
+ final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
LinkProperties lp = captor.getValue();
assertNotNull(lp);
@@ -809,6 +808,7 @@
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
mtu, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, mtu);
if (shouldChangeMtu) {
@@ -996,9 +996,8 @@
assertEquals(5, packetList.size());
assertArpProbe(packetList.get(0));
assertArpAnnounce(packetList.get(3));
-
- ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
- verifyProvisioningSuccess(captor, Collections.singletonList(CLIENT_ADDR));
+ } else {
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime,
TEST_DEFAULT_MTU);
}
@@ -1037,6 +1036,7 @@
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
}
@@ -1056,6 +1056,7 @@
performDhcpHandshake(true /* isSuccessLease */, INFINITE_LEASE,
true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(INFINITE_LEASE, currentTime, TEST_DEFAULT_MTU);
}
@@ -1065,6 +1066,7 @@
performDhcpHandshake(true /* isSuccessLease */, null /* no lease time */,
true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(null, currentTime, TEST_DEFAULT_MTU);
}
@@ -1073,6 +1075,7 @@
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
false /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryNeverStoreNetworkAttributes();
}
@@ -1082,6 +1085,7 @@
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, true /* shouldReplyRapidCommitAck */,
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
}
@@ -1205,6 +1209,7 @@
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
TEST_MIN_MTU, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_MIN_MTU);
// Pretend that ConnectivityService set the MTU.
@@ -1221,6 +1226,7 @@
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
0 /* mtu */, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, 0 /* mtu */);
assertEquals(NetworkInterface.getByName(mIfaceName).getMTU(), TEST_DEFAULT_MTU);
}
@@ -1241,11 +1247,11 @@
fail("No router solicitation received on interface within timeout");
}
- private void sendBasicRouterAdvertisement(boolean waitForRs) throws Exception {
+ private void sendRouterAdvertisement(boolean waitForRs, short lifetime) throws Exception {
final String dnsServer = "2001:4860:4860::64";
final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
- ByteBuffer ra = buildRaPacket(pio, rdnss);
+ ByteBuffer ra = buildRaPacket(lifetime, pio, rdnss);
if (waitForRs) {
waitForRouterSolicitation();
@@ -1254,6 +1260,14 @@
mPacketReader.sendResponse(ra);
}
+ private void sendBasicRouterAdvertisement(boolean waitForRs) throws Exception {
+ sendRouterAdvertisement(waitForRs, (short) 1800);
+ }
+
+ private void sendRouterAdvertisementWithZeroLifetime() throws Exception {
+ sendRouterAdvertisement(false /* waitForRs */, (short) 0);
+ }
+
// TODO: move this and the following method to a common location and use them in ApfTest.
private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString)
throws Exception {
@@ -1315,7 +1329,8 @@
return checksumAdjust(checksum, (short) IPPROTO_TCP, (short) IPPROTO_ICMPV6);
}
- private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
+ private static ByteBuffer buildRaPacket(short lifetime, ByteBuffer... options)
+ throws Exception {
final MacAddress srcMac = MacAddress.fromString("33:33:00:00:00:01");
final MacAddress dstMac = MacAddress.fromString("01:02:03:04:05:06");
final byte[] routerLinkLocal = InetAddresses.parseNumericAddress("fe80::1").getAddress();
@@ -1343,7 +1358,7 @@
packet.putShort((short) 0); // Checksum, TBD
packet.put((byte) 0); // Hop limit, unspecified
packet.put((byte) 0); // M=0, O=0
- packet.putShort((short) 1800); // Router lifetime
+ packet.putShort(lifetime); // Router lifetime
packet.putInt(0); // Reachable time, unspecified
packet.putInt(100); // Retrans time 100ms.
@@ -1363,6 +1378,10 @@
return packet;
}
+ private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
+ return buildRaPacket((short) 1800, options);
+ }
+
private void disableIpv6ProvisioningDelays() throws Exception {
// Speed up the test by disabling DAD and removing router_solicitation_delay.
// We don't need to restore the default value because the interface is removed in tearDown.
@@ -1640,12 +1659,12 @@
HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
}
- @Test
- public void testIpClientClearingIpAddressState() throws Exception {
+ private void doIPv4OnlyProvisioningAndExitWithLeftAddress() throws Exception {
final long currentTime = System.currentTimeMillis();
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* shouldReplyRapidCommitAck */,
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
// Stop IpClient and expect a final LinkProperties callback with an empty LP.
@@ -1665,6 +1684,11 @@
// TODO: once IpClient gets IP addresses directly from netlink instead of from netd, it
// may be sufficient to call waitForIdle to see if IpClient has seen the address.
addIpAddressAndWaitForIt(mIfaceName);
+ }
+
+ @Test
+ public void testIpClientClearingIpAddressState() throws Exception {
+ doIPv4OnlyProvisioningAndExitWithLeftAddress();
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withoutIpReachabilityMonitor()
@@ -1684,6 +1708,22 @@
}
@Test
+ public void testIpClientClearingIpAddressState_enablePreconnection() throws Exception {
+ doIPv4OnlyProvisioningAndExitWithLeftAddress();
+
+ // Enter ClearingIpAddressesState to clear the remaining IPv4 addresses and transition to
+ // PreconnectionState instead of RunningState.
+ startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
+ false /* shouldReplyRapidCommitAck */, true /* isDhcpPreConnectionEnabled */,
+ false /* isDhcpIpConflictDetectEnabled */);
+ assertDiscoverPacketOnPreconnectionStart();
+
+ // Force to enter RunningState.
+ mIpc.notifyPreconnectionComplete(false /* abort */);
+ HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+ }
+
+ @Test
public void testDhcpClientPreconnection_success() throws Exception {
doIpClientProvisioningWithPreconnectionTest(true /* shouldReplyRapidCommitAck */,
false /* shouldAbortPreconnection */, false /* shouldFirePreconnectionTimeout */,
@@ -1858,6 +1898,7 @@
true /* isHostnameConfigurationEnabled */, TEST_HOST_NAME /* hostname */,
null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
assertEquals(2, sentPackets.size());
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertHostname(true, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
}
@@ -1872,6 +1913,7 @@
false /* isHostnameConfigurationEnabled */, TEST_HOST_NAME,
null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
assertEquals(2, sentPackets.size());
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertHostname(false, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
}
@@ -1886,6 +1928,7 @@
true /* isHostnameConfigurationEnabled */, null /* hostname */,
null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
assertEquals(2, sentPackets.size());
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertHostname(true, null /* hostname */, null /* hostnameAfterTransliteration */,
sentPackets);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
@@ -1906,8 +1949,7 @@
(short) TEST_DEFAULT_MTU, serverSentUrl));
final int testMtu = 1345;
handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
- false /* isDhcpRapidCommitEnabled */, testMtu,
- false /* isDhcpIpConflictDetectEnabled */, serverSentUrl);
+ false /* shouldReplyRapidCommitAck */, testMtu, serverSentUrl);
final Uri expectedUrl = featureEnabled && serverSendsOption
? Uri.parse(TEST_CAPTIVE_PORTAL_URL) : null;
@@ -1976,6 +2018,7 @@
false /* isHostnameConfigurationEnabled */, null /* hostname */,
null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */);
assertEquals(2, sentPackets.size());
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
ArgumentCaptor<DhcpResultsParcelable> captor =
ArgumentCaptor.forClass(DhcpResultsParcelable.class);
@@ -2070,6 +2113,7 @@
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */,
true /* isHostnameConfigurationEnabled */, null /* hostname */,
null /* captivePortalApiUrl */, displayName, scanResultInfo);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
// simulate the roaming by updating bssid.
@@ -2144,4 +2188,73 @@
doDhcpRoamingTest(true /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
}
+
+ private void doDualStackProvisioning() throws Exception {
+ when(mCm.shouldAvoidBadWifi()).thenReturn(true);
+
+ final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+ .withoutIpReachabilityMonitor()
+ .build();
+ // Accelerate DHCP handshake to shorten test duration, not strictly necessary.
+ mDependencies.setDhcpRapidCommitEnabled(true);
+ mIpc.startProvisioning(config);
+
+ final InOrder inOrder = inOrder(mCb);
+ final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+ final String dnsServer = "2001:4860:4860::64";
+ final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
+ final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+ final ByteBuffer ra = buildRaPacket(pio, rdnss);
+
+ doIpv6OnlyProvisioning(inOrder, ra);
+
+ // Start IPv4 provisioning and wait until entire provisioning completes.
+ handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+ true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(argThat(x -> {
+ if (!x.isIpv4Provisioned() || !x.isIpv6Provisioned()) return false;
+ lpFuture.complete(x);
+ return true;
+ }));
+
+ final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertNotNull(lp);
+ assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer)));
+ assertTrue(lp.getDnsServers().contains(SERVER_ADDR));
+
+ reset(mCb);
+ }
+
+ @Test
+ public void testIgnoreIpv6ProvisioningLoss() throws Exception {
+ doDualStackProvisioning();
+
+ final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+
+ // Send RA with 0-lifetime and wait until all IPv6-related default route and DNS servers
+ // have been removed, then verify if there is IPv4-only info left in the LinkProperties.
+ sendRouterAdvertisementWithZeroLifetime();
+ verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(
+ argThat(x -> {
+ final boolean isOnlyIPv4Provisioned = (x.getLinkAddresses().size() == 1
+ && x.getDnsServers().size() == 1
+ && x.getAddresses().get(0) instanceof Inet4Address
+ && x.getDnsServers().get(0) instanceof Inet4Address);
+
+ if (!isOnlyIPv4Provisioned) return false;
+ lpFuture.complete(x);
+ return true;
+ }));
+ final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertNotNull(lp);
+ assertEquals(lp.getAddresses().get(0), CLIENT_ADDR);
+ assertEquals(lp.getDnsServers().get(0), SERVER_ADDR);
+ }
+
+ @Test
+ public void testDualStackProvisioning() throws Exception {
+ doDualStackProvisioning();
+
+ verify(mCb, never()).onProvisioningFailure(any());
+ }
}
diff --git a/tests/lib/multivariant/com/android/testutils/PacketFilter.kt b/tests/lib/multivariant/com/android/testutils/PacketFilter.kt
new file mode 100644
index 0000000..cd8d6a5
--- /dev/null
+++ b/tests/lib/multivariant/com/android/testutils/PacketFilter.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.testutils
+
+import java.util.function.Predicate
+
+const val ETHER_TYPE_OFFSET = 12
+const val ETHER_HEADER_LENGTH = 14
+const val IPV4_PROTOCOL_OFFSET = ETHER_HEADER_LENGTH + 9
+const val IPV4_CHECKSUM_OFFSET = ETHER_HEADER_LENGTH + 10
+const val IPV4_HEADER_LENGTH = 20
+const val IPV4_UDP_OFFSET = ETHER_HEADER_LENGTH + IPV4_HEADER_LENGTH
+const val BOOTP_OFFSET = IPV4_UDP_OFFSET + 8
+const val BOOTP_TID_OFFSET = BOOTP_OFFSET + 4
+const val BOOTP_CLIENT_MAC_OFFSET = BOOTP_OFFSET + 28
+const val DHCP_OPTIONS_OFFSET = BOOTP_OFFSET + 240
+
+/**
+ * A [Predicate] that matches a [ByteArray] if it contains the specified [bytes] at the specified
+ * [offset].
+ */
+class OffsetFilter(val offset: Int, vararg val bytes: Byte) : Predicate<ByteArray> {
+ override fun test(packet: ByteArray) =
+ bytes.withIndex().all { it.value == packet[offset + it.index] }
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped packets that contain an UDP over IPv4 datagram.
+ */
+class IPv4UdpFilter : Predicate<ByteArray> {
+ private val impl = OffsetFilter(ETHER_TYPE_OFFSET, 0x08, 0x00 /* IPv4 */).and(
+ OffsetFilter(IPV4_PROTOCOL_OFFSET, 17 /* UDP */))
+ override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches ethernet-encapped DHCP packets sent from a DHCP client.
+ */
+class DhcpClientPacketFilter : Predicate<ByteArray> {
+ private val impl = IPv4UdpFilter()
+ .and(OffsetFilter(IPV4_UDP_OFFSET /* source port */, 0x00, 0x44 /* 68 */))
+ .and(OffsetFilter(IPV4_UDP_OFFSET + 2 /* dest port */, 0x00, 0x43 /* 67 */))
+ override fun test(t: ByteArray) = impl.test(t)
+}
+
+/**
+ * A [Predicate] that matches a [ByteArray] if it contains a ethernet-encapped DHCP packet that
+ * contains the specified option with the specified [bytes] as value.
+ */
+class DhcpOptionFilter(val option: Byte, vararg val bytes: Byte) : Predicate<ByteArray> {
+ override fun test(packet: ByteArray): Boolean {
+ val option = findDhcpOption(packet, option) ?: return false
+ return option.contentEquals(bytes)
+ }
+}
+
+/**
+ * Find a DHCP option in a packet and return its value, if found.
+ */
+fun findDhcpOption(packet: ByteArray, option: Byte): ByteArray? =
+ findOptionOffset(packet, option, DHCP_OPTIONS_OFFSET)?.let {
+ val optionLen = packet[it + 1]
+ return packet.copyOfRange(it + 2 /* type, length bytes */, it + 2 + optionLen)
+ }
+
+private tailrec fun findOptionOffset(packet: ByteArray, option: Byte, searchOffset: Int): Int? {
+ if (packet.size <= searchOffset + 2 /* type, length bytes */) return null
+
+ return if (packet[searchOffset] == option) searchOffset else {
+ val optionLen = packet[searchOffset + 1]
+ findOptionOffset(packet, option, searchOffset + 2 + optionLen)
+ }
+}
diff --git a/tests/lib/multivariant/com/android/testutils/SkipPresubmit.kt b/tests/lib/multivariant/com/android/testutils/SkipPresubmit.kt
new file mode 100644
index 0000000..69ed048
--- /dev/null
+++ b/tests/lib/multivariant/com/android/testutils/SkipPresubmit.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.testutils
+
+/**
+ * Skip the test in presubmit runs for the reason specified in [reason].
+ *
+ * This annotation is typically used to document hardware or test bench limitations.
+ */
+annotation class SkipPresubmit(val reason: String)
\ No newline at end of file
diff --git a/tests/lib/src/com/android/testutils/TapPacketReader.java b/tests/lib/src/com/android/testutils/TapPacketReader.java
index c68e5a5..e55ed44 100644
--- a/tests/lib/src/com/android/testutils/TapPacketReader.java
+++ b/tests/lib/src/com/android/testutils/TapPacketReader.java
@@ -19,6 +19,7 @@
import android.net.util.PacketReader;
import android.os.Handler;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
@@ -26,12 +27,16 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+import kotlin.Lazy;
+import kotlin.LazyKt;
public class TapPacketReader extends PacketReader {
private final FileDescriptor mTapFd;
- private final LinkedBlockingQueue<byte[]> mReceivedPackets = new LinkedBlockingQueue<byte[]>();
+ private final ArrayTrackRecord<byte[]> mReceivedPackets = new ArrayTrackRecord<>();
+ private final Lazy<ArrayTrackRecord<byte[]>.ReadHead> mReadHead =
+ LazyKt.lazy(mReceivedPackets::newReadHead);
public TapPacketReader(Handler h, FileDescriptor tapFd, int maxPacketSize) {
super(h, maxPacketSize);
@@ -46,23 +51,25 @@
@Override
protected void handlePacket(byte[] recvbuf, int length) {
final byte[] newPacket = Arrays.copyOf(recvbuf, length);
- if (!mReceivedPackets.offer(newPacket)) {
+ if (!mReceivedPackets.add(newPacket)) {
throw new AssertionError("More than " + Integer.MAX_VALUE + " packets outstanding!");
}
}
/**
* Get the next packet that was received on the interface.
- *
*/
@Nullable
public byte[] popPacket(long timeoutMs) {
- try {
- return mReceivedPackets.poll(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // Fall through
- }
- return null;
+ return mReadHead.getValue().poll(timeoutMs, packet -> true);
+ }
+
+ /**
+ * Get the next packet that was received on the interface and matches the specified filter.
+ */
+ @Nullable
+ public byte[] popPacket(long timeoutMs, @NonNull Predicate<byte[]> filter) {
+ return mReadHead.getValue().poll(timeoutMs, filter::test);
}
public void sendResponse(final ByteBuffer packet) throws IOException {
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index d85d059..6e969ec 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -51,7 +51,6 @@
import android.net.ip.IpClient.IpClientCallbacksWrapper;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.RaEvent;
-import android.net.shared.Inet4AddressUtils;
import android.net.util.InterfaceParams;
import android.net.util.SharedLog;
import android.os.ConditionVariable;
@@ -67,6 +66,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.HexDump;
+import com.android.net.module.util.Inet4AddressUtils;
import com.android.networkstack.apishim.NetworkInformationShimImpl;
import com.android.server.networkstack.tests.R;
import com.android.server.util.NetworkStackConstants;
diff --git a/tests/unit/src/android/net/dhcp/DhcpLeaseRepositoryTest.java b/tests/unit/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
index 3a6a890..81685ef 100644
--- a/tests/unit/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
@@ -20,8 +20,8 @@
import static android.net.dhcp.DhcpLease.HOSTNAME_NONE;
import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC;
import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
import static org.junit.Assert.assertEquals;
diff --git a/tests/unit/src/android/net/dhcp/DhcpLeaseTest.kt b/tests/unit/src/android/net/dhcp/DhcpLeaseTest.kt
index 2971f65..743418c 100644
--- a/tests/unit/src/android/net/dhcp/DhcpLeaseTest.kt
+++ b/tests/unit/src/android/net/dhcp/DhcpLeaseTest.kt
@@ -18,7 +18,7 @@
import android.net.InetAddresses.parseNumericAddress
import android.net.MacAddress
-import android.net.shared.Inet4AddressUtils.intToInet4AddressHTH
+import com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertFieldCountEquals
diff --git a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
index c565238..9d2a630 100644
--- a/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpPacketTest.java
@@ -34,8 +34,9 @@
import static android.net.dhcp.DhcpPacket.INADDR_ANY;
import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
import static android.net.dhcp.DhcpPacket.ParseException;
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+
+import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress;
+import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
diff --git a/tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
similarity index 91%
rename from tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java
rename to tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
index 532cb64..7d899e0 100644
--- a/tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpResultsParcelableUtilTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package android.net.shared;
+package android.net.dhcp;
import static android.net.InetAddresses.parseNumericAddress;
-import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
-import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
+import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
@@ -26,6 +26,7 @@
import android.net.DhcpResults;
import android.net.LinkAddress;
+import android.net.shared.IpConfigurationParcelableUtil;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -41,7 +42,7 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class IpConfigurationParcelableUtilTest {
+public class DhcpResultsParcelableUtilTest {
private DhcpResults mDhcpResults;
@Before
diff --git a/tests/unit/src/android/net/dhcp/DhcpServerTest.java b/tests/unit/src/android/net/dhcp/DhcpServerTest.java
index 2e8a3c2..d313b94 100644
--- a/tests/unit/src/android/net/dhcp/DhcpServerTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpServerTest.java
@@ -23,9 +23,10 @@
import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
import static android.net.dhcp.DhcpServer.CMD_RECEIVE_PACKET;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -51,12 +52,12 @@
import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException;
import android.net.dhcp.DhcpServer.Clock;
import android.net.dhcp.DhcpServer.Dependencies;
-import android.net.shared.Inet4AddressUtils;
import android.net.util.SharedLog;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
+import com.android.net.module.util.Inet4AddressUtils;
import com.android.testutils.HandlerUtilsKt;
import org.junit.After;
diff --git a/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java b/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
index 1ce2f82..6506eba 100644
--- a/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
+++ b/tests/unit/src/android/net/dhcp/DhcpServingParamsTest.java
@@ -18,7 +18,8 @@
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.dhcp.DhcpServingParams.MTU_UNSET;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -28,11 +29,11 @@
import android.annotation.Nullable;
import android.net.LinkAddress;
import android.net.dhcp.DhcpServingParams.InvalidParameterException;
-import android.net.shared.Inet4AddressUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.Inet4AddressUtils;
import com.android.testutils.MiscAssertsKt;
import org.junit.Before;
diff --git a/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java b/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java
deleted file mode 100644
index 35f8c79..0000000
--- a/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java
+++ /dev/null
@@ -1,210 +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.shared;
-
-import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
-import static android.net.shared.Inet4AddressUtils.getImplicitNetmask;
-import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
-import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTL;
-import static android.net.shared.Inet4AddressUtils.netmaskToPrefixLength;
-import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
-import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.fail;
-
-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.Inet4Address;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class Inet4AddressUtilsTest {
-
- @Test
- public void testInet4AddressToIntHTL() {
- assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0")));
- assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0")));
- assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0")));
- assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0")));
- assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254")));
- assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255")));
- }
-
- @Test
- public void testIntToInet4AddressHTL() {
- assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0));
- assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff));
- assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a));
- assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a));
- assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0));
- assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0));
- }
-
- @Test
- public void testInet4AddressToIntHTH() {
- assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0")));
- assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0")));
- assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0")));
- assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0")));
- assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254")));
- assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255")));
- }
-
- @Test
- public void testIntToInet4AddressHTH() {
- assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0));
- assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000));
- assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000));
- assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00));
- assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe));
- assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff));
- }
-
-
- @Test
- public void testPrefixLengthToV4NetmaskIntHTL() {
- assertEquals(0, prefixLengthToV4NetmaskIntHTL(0));
- assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9));
- assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17));
- assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23));
- assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31));
- assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32));
- }
-
- @Test
- public void testPrefixLengthToV4NetmaskIntHTH() {
- assertEquals(0, prefixLengthToV4NetmaskIntHTH(0));
- assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9));
- assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17));
- assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23));
- assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31));
- assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32));
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() {
- prefixLengthToV4NetmaskIntHTH(-1);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() {
- prefixLengthToV4NetmaskIntHTH(33);
- }
-
- private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) {
- final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength);
- final int addrInt = inet4AddressToIntHTH(ipv4Address(addr));
- assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt));
- }
-
- @Test
- public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() {
- checkAddressMasking("192.168.0.0", "192.168.128.1", 16);
- checkAddressMasking("255.240.0.0", "255.255.255.255", 12);
- checkAddressMasking("255.255.255.255", "255.255.255.255", 32);
- checkAddressMasking("0.0.0.0", "255.255.255.255", 0);
- }
-
- @Test
- public void testGetImplicitNetmask() {
- assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2")));
- assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7")));
- assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105")));
- assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145")));
- assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1")));
- assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1")));
- assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1")));
- assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8")));
- }
-
- private void assertInvalidNetworkMask(Inet4Address addr) {
- try {
- netmaskToPrefixLength(addr);
- fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @Test
- public void testNetmaskToPrefixLength() {
- assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0")));
- assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0")));
- assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0")));
- assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0")));
- assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254")));
- assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255")));
-
- assertInvalidNetworkMask(ipv4Address("0.0.0.1"));
- assertInvalidNetworkMask(ipv4Address("255.255.255.253"));
- assertInvalidNetworkMask(ipv4Address("255.255.0.255"));
- }
-
- @Test
- public void testGetPrefixMaskAsAddress() {
- assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress());
- assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress());
- assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress());
- assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress());
- }
-
- @Test
- public void testGetBroadcastAddress() {
- assertEquals("192.168.15.255",
- getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress());
- assertEquals("192.255.255.255",
- getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress());
- assertEquals("192.168.0.123",
- getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress());
- assertEquals("255.255.255.255",
- getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testGetBroadcastAddress_PrefixTooLarge() {
- getBroadcastAddress(ipv4Address("192.168.0.123"), 33);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testGetBroadcastAddress_NegativePrefix() {
- getBroadcastAddress(ipv4Address("192.168.0.123"), -1);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testGetPrefixMaskAsAddress_PrefixTooLarge() {
- getPrefixMaskAsInet4Address(33);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testGetPrefixMaskAsAddress_NegativePrefix() {
- getPrefixMaskAsInet4Address(-1);
- }
-
- private Inet4Address ipv4Address(String addr) {
- return (Inet4Address) InetAddresses.parseNumericAddress(addr);
- }
-}
diff --git a/tests/unit/src/android/net/shared/NetdUtilsTest.java b/tests/unit/src/android/net/shared/NetdUtilsTest.java
new file mode 100644
index 0000000..f3ef53a
--- /dev/null
+++ b/tests/unit/src/android/net/shared/NetdUtilsTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.shared;
+
+import static android.net.INetd.LOCAL_NET_ID;
+import static android.system.OsConstants.EBUSY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+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.verifyNoMoreInteractions;
+
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+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;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class NetdUtilsTest {
+ private static final String IFACE_NAME = "testnet1";
+ private static final IpPrefix TEST_IPPREFIX = new IpPrefix("192.168.42.1/24");
+
+ @Mock private INetd mNetd;
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private void setNetworkAddInterfaceOutcome(final Exception cause, int numLoops)
+ throws Exception {
+ // This cannot be an int because local variables referenced from a lambda expression must
+ // be final or effectively final.
+ final Counter myCounter = new Counter();
+ doAnswer((invocation) -> {
+ myCounter.count();
+ if (myCounter.isCounterReached(numLoops)) {
+ if (cause == null) return null;
+
+ throw cause;
+ }
+
+ throw new ServiceSpecificException(EBUSY);
+ }).when(mNetd).networkAddInterface(LOCAL_NET_ID, IFACE_NAME);
+ }
+
+ class Counter {
+ private int mValue = 0;
+
+ private void count() {
+ mValue++;
+ }
+
+ private boolean isCounterReached(int target) {
+ return mValue >= target;
+ }
+ }
+
+ private void verifyTetherInterfaceSucceeds(int expectedTries) throws Exception {
+ setNetworkAddInterfaceOutcome(null, expectedTries);
+
+ NetdUtils.tetherInterface(mNetd, IFACE_NAME, TEST_IPPREFIX);
+ verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+ verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE_NAME);
+ verify(mNetd, times(2)).networkAddRoute(eq(LOCAL_NET_ID), eq(IFACE_NAME), any(), any());
+ verifyNoMoreInteractions(mNetd);
+ reset(mNetd);
+ }
+
+ @Test
+ public void testTetherInterfaceSuccessful() throws Exception {
+ // Expect #networkAddInterface successful at first tries.
+ verifyTetherInterfaceSucceeds(1);
+
+ // Expect #networkAddInterface successful after 10 tries.
+ verifyTetherInterfaceSucceeds(10);
+ }
+
+ private void runTetherInterfaceWithServiceSpecificException(int expectedTries,
+ int expectedCode) throws Exception {
+ setNetworkAddInterfaceOutcome(new ServiceSpecificException(expectedCode), expectedTries);
+
+ try {
+ NetdUtils.tetherInterface(mNetd, IFACE_NAME, TEST_IPPREFIX, 20, 0);
+ fail("Expect throw ServiceSpecificException");
+ } catch (ServiceSpecificException e) {
+ assertEquals(e.errorCode, expectedCode);
+ }
+
+ verifyNetworkAddInterfaceFails(expectedTries);
+ reset(mNetd);
+ }
+
+ private void runTetherInterfaceWithRemoteException(int expectedTries) throws Exception {
+ setNetworkAddInterfaceOutcome(new RemoteException(), expectedTries);
+
+ try {
+ NetdUtils.tetherInterface(mNetd, IFACE_NAME, TEST_IPPREFIX, 20, 0);
+ fail("Expect throw RemoteException");
+ } catch (RemoteException e) { }
+
+ verifyNetworkAddInterfaceFails(expectedTries);
+ reset(mNetd);
+ }
+
+ private void verifyNetworkAddInterfaceFails(int expectedTries) throws Exception {
+ verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+ verify(mNetd, times(expectedTries)).networkAddInterface(LOCAL_NET_ID, IFACE_NAME);
+ verify(mNetd, never()).networkAddRoute(anyInt(), anyString(), any(), any());
+ verifyNoMoreInteractions(mNetd);
+ }
+
+ @Test
+ public void testTetherInterfaceFailOnNetworkAddInterface() throws Exception {
+ // Test throwing ServiceSpecificException with EBUSY failure.
+ runTetherInterfaceWithServiceSpecificException(20, EBUSY);
+
+ // Test throwing ServiceSpecificException with unexpectedError.
+ final int unexpectedError = 999;
+ runTetherInterfaceWithServiceSpecificException(1, unexpectedError);
+
+ // Test throwing ServiceSpecificException with unexpectedError after 7 tries.
+ runTetherInterfaceWithServiceSpecificException(7, unexpectedError);
+
+ // Test throwing RemoteException.
+ runTetherInterfaceWithRemoteException(1);
+
+ // Test throwing RemoteException after 3 tries.
+ runTetherInterfaceWithRemoteException(3);
+ }
+}
diff --git a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
index 1be6ee7..348392d 100644
--- a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
+++ b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
@@ -20,8 +20,9 @@
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_NONE
import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context
import android.content.Intent
import android.content.res.Resources
@@ -56,6 +57,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.intThat
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -80,6 +82,8 @@
@Mock
private lateinit var mNm: NotificationManager
@Mock
+ private lateinit var mNotificationChannelsNm: NotificationManager
+ @Mock
private lateinit var mCm: ConnectivityManager
@Mock
private lateinit var mResources: Resources
@@ -141,10 +145,12 @@
realContext.packageName, 0, UserHandle.ALL)
mAllUserContext.mockService(Context.NOTIFICATION_SERVICE, NotificationManager::class, mNm)
+ mContext.mockService(Context.NOTIFICATION_SERVICE, NotificationManager::class,
+ mNotificationChannelsNm)
mContext.mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class, mCm)
doReturn(NotificationChannel(CHANNEL_VENUE_INFO, "TestChannel", IMPORTANCE_DEFAULT))
- .`when`(mNm).getNotificationChannel(CHANNEL_VENUE_INFO)
+ .`when`(mNotificationChannelsNm).getNotificationChannel(CHANNEL_VENUE_INFO)
doReturn(mPendingIntent).`when`(mDependencies).getActivityPendingIntent(
any(), any(), anyInt())
@@ -183,7 +189,8 @@
assertEquals(CHANNEL_CONNECTED, note.channelId)
assertEquals(timeout, note.timeoutAfter)
verify(mDependencies).getActivityPendingIntent(
- eq(mCurrentUserContext), mIntentCaptor.capture(), eq(FLAG_UPDATE_CURRENT))
+ eq(mCurrentUserContext), mIntentCaptor.capture(),
+ intThat { it or FLAG_IMMUTABLE != 0 })
}
private fun verifyCanceledNotificationAfterNetworkLost() {
@@ -247,7 +254,8 @@
fun testConnectedVenueInfoNotification_VenueInfoDisabled() {
// Venue info (CaptivePortalData) is not available for API <= Q
assumeTrue(NetworkInformationShimImpl.useApiAboveQ())
- doReturn(null).`when`(mNm).getNotificationChannel(CHANNEL_VENUE_INFO)
+ val channel = NotificationChannel(CHANNEL_VENUE_INFO, "test channel", IMPORTANCE_NONE)
+ doReturn(channel).`when`(mNotificationChannelsNm).getNotificationChannel(CHANNEL_VENUE_INFO)
mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
onLinkPropertiesChanged(mTestCapportLp)
onDefaultNetworkAvailable(TEST_NETWORK)
@@ -273,7 +281,8 @@
verify(mNm).notify(eq(TEST_NETWORK_TAG), mNoteIdCaptor.capture(), mNoteCaptor.capture())
verify(mDependencies).getActivityPendingIntent(
- eq(mCurrentUserContext), mIntentCaptor.capture(), eq(FLAG_UPDATE_CURRENT))
+ eq(mCurrentUserContext), mIntentCaptor.capture(),
+ intThat { it or FLAG_IMMUTABLE != 0 })
verifyVenueInfoIntent(mIntentCaptor.value)
verifyCanceledNotificationAfterDefaultNetworkLost()
}
diff --git a/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt b/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
index 4c62071..578e6ca 100644
--- a/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
+++ b/tests/unit/src/com/android/networkstack/metrics/DataStallStatsUtilsTest.kt
@@ -16,18 +16,33 @@
package com.android.networkstack.metrics
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.captiveportal.CaptivePortalProbeResult
+import android.net.metrics.ValidationProbeEvent
+import android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_DNS
+import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.server.connectivity.nano.CellularData
import com.android.server.connectivity.nano.DataStallEventProto
+import com.android.server.connectivity.nano.DnsEvent
+import com.google.protobuf.nano.MessageNano
+import java.util.Arrays
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import android.net.metrics.ValidationProbeEvent
+import org.mockito.ArgumentMatchers.eq
@RunWith(AndroidJUnit4::class)
@SmallTest
class DataStallStatsUtilsTest {
+ private val TEST_ELAPSED_TIME_MS = 123456789L
+ private val TEST_MCCMNC = "123456"
+ private val TEST_SIGNAL_STRENGTH = -100
+ private val RETURN_CODE_DNS_TIMEOUT = 255
+
@Test
fun testProbeResultToEnum() {
assertEquals(DataStallStatsUtils.probeResultToEnum(null), DataStallEventProto.INVALID)
@@ -53,4 +68,61 @@
CaptivePortalProbeResult.PORTAL_CODE, ValidationProbeEvent.PROBE_HTTPS)),
DataStallEventProto.PORTAL)
}
+
+ @Test
+ fun testWrite() {
+ val session = mockitoSession().spyStatic(NetworkStackStatsLog::class.java).startMocking()
+ val stats = DataStallDetectionStats.Builder()
+ .setEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS)
+ .setNetworkType(TRANSPORT_CELLULAR)
+ .setCellData(TelephonyManager.NETWORK_TYPE_LTE /* radioType */,
+ true /* roaming */,
+ TEST_MCCMNC /* networkMccmnc */,
+ TEST_MCCMNC /* simMccmnc */,
+ TEST_SIGNAL_STRENGTH /* signalStrength */)
+ .setTcpFailRate(90)
+ .setTcpSentSinceLastRecv(10)
+ generateTimeoutDnsEvent(stats, count = 5)
+ DataStallStatsUtils.write(stats.build(), CaptivePortalProbeResult.PARTIAL)
+
+ verify { NetworkStackStatsLog.write(
+ eq(NetworkStackStatsLog.DATA_STALL_EVENT),
+ eq(DATA_STALL_EVALUATION_TYPE_DNS),
+ eq(DataStallEventProto.PARTIAL),
+ eq(TRANSPORT_CELLULAR),
+ eq(DataStallDetectionStats.emptyWifiInfoIfNull(null)),
+ eq(makeTestCellDataNano()),
+ eq(makeTestDnsTimeoutNano(5)),
+ eq(90) /* tcpFailRate */,
+ eq(10) /* tcpSentSinceLastRecv */) }
+
+ session.finishMocking()
+ }
+
+ private fun makeTestDnsTimeoutNano(timeoutCount: Int): ByteArray? {
+ // Make an expected nano dns message.
+ val event = DnsEvent()
+ event.dnsReturnCode = IntArray(timeoutCount)
+ event.dnsTime = LongArray(timeoutCount)
+ Arrays.fill(event.dnsReturnCode, RETURN_CODE_DNS_TIMEOUT)
+ Arrays.fill(event.dnsTime, TEST_ELAPSED_TIME_MS)
+ return MessageNano.toByteArray(event)
+ }
+
+ private fun makeTestCellDataNano(): ByteArray? {
+ // Make an expected nano cell data message.
+ val data = CellularData()
+ data.ratType = DataStallEventProto.RADIO_TECHNOLOGY_LTE
+ data.networkMccmnc = TEST_MCCMNC
+ data.simMccmnc = TEST_MCCMNC
+ data.isRoaming = true
+ data.signalStrength = TEST_SIGNAL_STRENGTH
+ return MessageNano.toByteArray(data)
+ }
+
+ private fun generateTimeoutDnsEvent(stats: DataStallDetectionStats.Builder, count: Int) {
+ repeat(count) {
+ stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, TEST_ELAPSED_TIME_MS)
+ }
+ }
}
diff --git a/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
new file mode 100644
index 0000000..01d94e2
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+import android.net.dhcp.DhcpPacket;
+import android.net.metrics.DhcpErrorEvent;
+import android.stats.connectivity.DhcpErrorCode;
+import android.stats.connectivity.DhcpFeature;
+import android.stats.connectivity.DisconnectCode;
+import android.stats.connectivity.HostnameTransResult;
+
+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;
+
+
+/**
+ * Tests for IpProvisioningMetrics.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkIpProvisioningMetricsTest {
+ @Test
+ public void testIpProvisioningMetrics_setHostnameTransinfo() throws Exception {
+ NetworkIpProvisioningReported mStats;
+ final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+
+ mMetrics.reset();
+ mMetrics.setHostnameTransinfo(false /* isOptionEnabled */, false /* transSuccess */);
+ mStats = mMetrics.statsWrite();
+ assertEquals(HostnameTransResult.HTR_DISABLE, mStats.getDhcpSession().getHtResult());
+
+ mMetrics.reset();
+ mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, false /* transSuccess */);
+ mStats = mMetrics.statsWrite();
+ assertEquals(HostnameTransResult.HTR_FAILURE, mStats.getDhcpSession().getHtResult());
+
+ mMetrics.reset();
+ mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, true /* transSuccess */);
+ mStats = mMetrics.statsWrite();
+ assertEquals(HostnameTransResult.HTR_SUCCESS, mStats.getDhcpSession().getHtResult());
+ }
+
+ @Test
+ public void testIpProvisioningMetrics_addDhcpErrorCode() throws Exception {
+ final NetworkIpProvisioningReported mStats;
+ final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+ mMetrics.reset();
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.BOOTP_TOO_SHORT);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.errorCodeWithOption(
+ DhcpErrorEvent.BUFFER_UNDERFLOW, DhcpPacket.DHCP_HOST_NAME));
+ // Write the incorrect input number 50000 as DHCP error number
+ int incorrectErrorCodeNumber = 50000;
+ mMetrics.addDhcpErrorCode(incorrectErrorCodeNumber);
+ for (int i = 0; i < mMetrics.MAX_DHCP_ERROR_COUNT; i++) {
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+ }
+ mStats = mMetrics.statsWrite();
+ assertEquals(DhcpErrorCode.ET_BOOTP_TOO_SHORT, mStats.getDhcpSession().getErrorCode(0));
+ assertEquals(DhcpErrorCode.ET_BUFFER_UNDERFLOW, mStats.getDhcpSession().getErrorCode(1));
+ // When the input is an invalid integer value (such as incorrectErrorCodeNumber),
+ // the DhcpErrorCode will be ET_UNKNOWN
+ assertEquals(DhcpErrorCode.ET_UNKNOWN, mStats.getDhcpSession().getErrorCode(2));
+ // If the same error code appears, it must be recorded
+ assertEquals(DhcpErrorCode.ET_PARSING_ERROR, mStats.getDhcpSession().getErrorCode(3));
+ assertEquals(DhcpErrorCode.ET_PARSING_ERROR, mStats.getDhcpSession().getErrorCode(4));
+ // The maximum number of DHCP error code counts is MAX_DHCP_ERROR_COUNT
+ assertEquals(mMetrics.MAX_DHCP_ERROR_COUNT, mStats.getDhcpSession().getErrorCodeCount());
+ }
+ @Test
+ public void testIpProvisioningMetrics_CollectMetrics() throws Exception {
+ final NetworkIpProvisioningReported mStats;
+ final IpProvisioningMetrics mMetrics = new IpProvisioningMetrics();
+ mMetrics.reset();
+ // Entering 3 DISCOVER_SEND_COUNTs
+ mMetrics.incrementCountForDiscover();
+ mMetrics.incrementCountForDiscover();
+ mMetrics.incrementCountForDiscover();
+
+ // Entering 2 SEND_REQUEST_COUNTs
+ mMetrics.incrementCountForRequest();
+ mMetrics.incrementCountForRequest();
+
+ // Entering 1 IP_CONFLICT_COUNT
+ mMetrics.incrementCountForIpConflict();
+
+ // Entering 4 DhcpFeatures and one is repeated, so it should only count to 3
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_INITREBOOT);
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_RAPIDCOMMIT);
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+ mMetrics.setDhcpEnabledFeature(DhcpFeature.DF_DAD);
+
+ // Entering 6 DhcpErrorCodes
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.L3_TOO_SHORT);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.RECEIVE_ERROR);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.RECEIVE_ERROR);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+ mMetrics.addDhcpErrorCode(DhcpErrorEvent.PARSING_ERROR);
+
+ mMetrics.setHostnameTransinfo(true /* isOptionEnabled */, true /* transSuccess */);
+
+ // Only the first IP provisioning disconnect code is recorded.
+ mMetrics.setDisconnectCode(DisconnectCode.DC_PROVISIONING_TIMEOUT);
+ mMetrics.setDisconnectCode(DisconnectCode.DC_ERROR_STARTING_IPV4);
+
+ mMetrics.setIPv4ProvisionedLatencyOnFirstTime(true);
+ mMetrics.setIPv6ProvisionedLatencyOnFirstTime(true);
+
+ // Writing the metrics into statsd
+ mStats = mMetrics.statsWrite();
+
+ // Verifing the result of the metrics.
+ assertEquals(3, mStats.getDhcpSession().getDiscoverCount());
+ assertEquals(2, mStats.getDhcpSession().getRequestCount());
+ assertEquals(1, mStats.getDhcpSession().getConflictCount());
+ assertEquals(3, mStats.getDhcpSession().getUsedFeaturesCount());
+ assertEquals(6, mStats.getDhcpSession().getErrorCodeCount());
+ assertEquals(HostnameTransResult.HTR_SUCCESS, mStats.getDhcpSession().getHtResult());
+ assertEquals(DisconnectCode.DC_PROVISIONING_TIMEOUT, mStats.getDisconnectCode());
+ assertTrue(mStats.getIpv4LatencyMicros() >= 0);
+ assertTrue(mStats.getIpv6LatencyMicros() >= 0);
+ assertTrue(mStats.getProvisioningDurationMicros() >= 0);
+ }
+
+ @Test
+ public void testIpProvisioningMetrics_VerifyConsecutiveMetricsLatency() throws Exception {
+ final IpProvisioningMetrics metrics = new IpProvisioningMetrics();
+ for (int i = 0; i < 2; i++) {
+ metrics.reset();
+ // delay 1 msec.
+ Thread.sleep(1);
+ metrics.setIPv4ProvisionedLatencyOnFirstTime(true);
+ metrics.setIPv6ProvisionedLatencyOnFirstTime(true);
+ NetworkIpProvisioningReported mStats = metrics.statsWrite();
+ // Each timer should be greater than 1000.
+ assertTrue(mStats.getIpv4LatencyMicros() >= 1000);
+ assertTrue(mStats.getIpv6LatencyMicros() >= 1000);
+ assertTrue(mStats.getProvisioningDurationMicros() >= 1000);
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java
new file mode 100644
index 0000000..98e7b63
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/metrics/NetworkValidationMetricsTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.metrics;
+
+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_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+
+import static org.junit.Assert.assertTrue;
+
+import android.net.INetworkMonitor;
+import android.net.NetworkCapabilities;
+import android.net.captiveportal.CaptivePortalProbeResult;
+import android.net.metrics.ValidationProbeEvent;
+import android.stats.connectivity.ProbeResult;
+import android.stats.connectivity.ProbeType;
+import android.stats.connectivity.TransportType;
+import android.stats.connectivity.ValidationResult;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.networkstack.apishim.CaptivePortalDataShimImpl;
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkValidationMetricsTest {
+ private static final String TEST_LOGIN_URL = "https://testportal.example.com/login";
+ private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info";
+ private static final int TTL_TOLERANCE_SECS = 10;
+
+ private static final NetworkCapabilities WIFI_CAPABILITIES =
+ new NetworkCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+
+ @Test
+ public void testNetworkValidationMetrics_VerifyProbeTypeToEnum() throws Exception {
+ verifyProbeType(ValidationProbeEvent.PROBE_DNS, ProbeType.PT_DNS);
+ verifyProbeType(ValidationProbeEvent.PROBE_HTTP, ProbeType.PT_HTTP);
+ verifyProbeType(ValidationProbeEvent.PROBE_HTTPS, ProbeType.PT_HTTPS);
+ verifyProbeType(ValidationProbeEvent.PROBE_PAC, ProbeType.PT_PAC);
+ verifyProbeType(ValidationProbeEvent.PROBE_FALLBACK, ProbeType.PT_FALLBACK);
+ verifyProbeType(ValidationProbeEvent.PROBE_PRIVDNS, ProbeType.PT_PRIVDNS);
+ }
+
+ private void verifyProbeType(int inputProbeType, ProbeType expectedEnumType) {
+ assertEquals(expectedEnumType, NetworkValidationMetrics.probeTypeToEnum(inputProbeType));
+ }
+
+ @Test
+ public void testNetworkValidationMetrics_VerifyHttpProbeResultToEnum() throws Exception {
+ verifyProbeType(new CaptivePortalProbeResult(CaptivePortalProbeResult.SUCCESS_CODE,
+ ValidationProbeEvent.PROBE_HTTP), ProbeResult.PR_SUCCESS);
+ verifyProbeType(new CaptivePortalProbeResult(CaptivePortalProbeResult.FAILED_CODE,
+ ValidationProbeEvent.PROBE_HTTP), ProbeResult.PR_FAILURE);
+ verifyProbeType(new CaptivePortalProbeResult(CaptivePortalProbeResult.PORTAL_CODE,
+ ValidationProbeEvent.PROBE_HTTP), ProbeResult.PR_PORTAL);
+ verifyProbeType(CaptivePortalProbeResult.PRIVATE_IP, ProbeResult.PR_PRIVATE_IP_DNS);
+ }
+
+ private void verifyProbeType(CaptivePortalProbeResult inputResult,
+ ProbeResult expectedResult) {
+ assertEquals(expectedResult, NetworkValidationMetrics.httpProbeResultToEnum(inputResult));
+ }
+
+ @Test
+ public void testNetworkValidationMetrics_VerifyValidationResultToEnum() throws Exception {
+ verifyProbeType(INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID, null,
+ ValidationResult.VR_SUCCESS);
+ verifyProbeType(0, TEST_LOGIN_URL, ValidationResult.VR_PORTAL);
+ verifyProbeType(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null,
+ ValidationResult.VR_PARTIAL);
+ verifyProbeType(0, null, ValidationResult.VR_FAILURE);
+ }
+
+ private void verifyProbeType(int inputResult, String inputRedirectUrl,
+ ValidationResult expectedResult) {
+ assertEquals(expectedResult, NetworkValidationMetrics.validationResultToEnum(inputResult,
+ inputRedirectUrl));
+ }
+
+ @Test
+ public void testNetworkValidationMetrics_VerifyTransportTypeToEnum() throws Exception {
+ final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
+ NetworkCapabilities nc = new NetworkCapabilities();
+ nc.addTransportType(TRANSPORT_WIFI);
+ assertEquals(TransportType.TT_WIFI, metrics.getTransportTypeFromNC(nc));
+ nc.addTransportType(TRANSPORT_VPN);
+ assertEquals(TransportType.TT_WIFI_VPN, metrics.getTransportTypeFromNC(nc));
+ nc.addTransportType(TRANSPORT_CELLULAR);
+ assertEquals(TransportType.TT_WIFI_CELLULAR_VPN, metrics.getTransportTypeFromNC(nc));
+
+ nc = new NetworkCapabilities();
+ nc.addTransportType(TRANSPORT_CELLULAR);
+ assertEquals(TransportType.TT_CELLULAR, metrics.getTransportTypeFromNC(nc));
+ nc.addTransportType(TRANSPORT_VPN);
+ assertEquals(TransportType.TT_CELLULAR_VPN, metrics.getTransportTypeFromNC(nc));
+
+ nc = new NetworkCapabilities();
+ nc.addTransportType(TRANSPORT_BLUETOOTH);
+ assertEquals(TransportType.TT_BLUETOOTH, metrics.getTransportTypeFromNC(nc));
+ nc.addTransportType(TRANSPORT_VPN);
+ assertEquals(TransportType.TT_BLUETOOTH_VPN, metrics.getTransportTypeFromNC(nc));
+
+ nc = new NetworkCapabilities();
+ nc.addTransportType(TRANSPORT_ETHERNET);
+ assertEquals(TransportType.TT_ETHERNET, metrics.getTransportTypeFromNC(nc));
+ nc.addTransportType(TRANSPORT_VPN);
+ assertEquals(TransportType.TT_ETHERNET_VPN, metrics.getTransportTypeFromNC(nc));
+ }
+
+ @Test
+ public void testNetworkValidationMetrics_VerifyConsecutiveProbeFailure() throws Exception {
+ final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
+ metrics.startCollection(WIFI_CAPABILITIES);
+ // 1. PT_DNS probe
+ metrics.addProbeEvent(ProbeType.PT_DNS, 1234, ProbeResult.PR_SUCCESS, null);
+ // 2. Consecutive PT_HTTP probe failure
+ for (int i = 0; i < 30; i++) {
+ metrics.addProbeEvent(ProbeType.PT_HTTP, 1234, ProbeResult.PR_FAILURE, null);
+ }
+
+ // Write metric into statsd
+ final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend();
+
+ // The maximum number of probe records should be the same as MAX_PROBE_EVENTS_COUNT
+ final ProbeEvents probeEvents = stats.getProbeEvents();
+ assertEquals(NetworkValidationMetrics.MAX_PROBE_EVENTS_COUNT,
+ probeEvents.getProbeEventCount());
+ }
+
+ @Test
+ public void testNetworkValidationMetrics_VerifyCollectMetrics() throws Exception {
+ final long bytesRemaining = 12_345L;
+ final long secondsRemaining = 3000L;
+ String apiContent = "{'captive': true,"
+ + "'user-portal-url': '" + TEST_LOGIN_URL + "',"
+ + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "',"
+ + "'bytes-remaining': " + bytesRemaining + ","
+ + "'seconds-remaining': " + secondsRemaining + "}";
+ final NetworkValidationMetrics metrics = new NetworkValidationMetrics();
+ final int validationIndex = 1;
+ final long longlatency = Integer.MAX_VALUE + 12344567L;
+ metrics.startCollection(WIFI_CAPABILITIES);
+
+ final JSONObject info = new JSONObject(apiContent);
+ final CaptivePortalDataShim captivePortalData = CaptivePortalDataShimImpl.isSupported()
+ ? CaptivePortalDataShimImpl.fromJson(info) : null;
+
+ // 1. PT_CAPPORT_API probe w CapportApiData info
+ metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_SUCCESS,
+ captivePortalData);
+ // 2. PT_CAPPORT_API probe w/o CapportApiData info
+ metrics.addProbeEvent(ProbeType.PT_CAPPORT_API, 1234, ProbeResult.PR_FAILURE, null);
+
+ // 3. PT_DNS probe
+ metrics.addProbeEvent(ProbeType.PT_DNS, 5678, ProbeResult.PR_FAILURE, null);
+
+ // 4. PT_HTTP probe
+ metrics.addProbeEvent(ProbeType.PT_HTTP, longlatency, ProbeResult.PR_PORTAL, null);
+
+ // add Validation result
+ metrics.setValidationResult(INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL, null);
+
+ // Write metric into statsd
+ final NetworkValidationReported stats = metrics.maybeStopCollectionAndSend();
+
+ // Verify: TransportType: WIFI
+ assertEquals(TransportType.TT_WIFI, stats.getTransportType());
+
+ // Verify: validationIndex
+ assertEquals(validationIndex, stats.getValidationIndex());
+
+ // probe Count: 4 (PT_CAPPORT_API, PT_DNS, PT_HTTP, PT_CAPPORT_API)
+ final ProbeEvents probeEvents = stats.getProbeEvents();
+ assertEquals(4, probeEvents.getProbeEventCount());
+
+ // Verify the 1st probe: ProbeType = PT_CAPPORT_API, Latency_us = 1234,
+ // ProbeResult = PR_SUCCESS, CapportApiData = capportData
+ ProbeEvent probeEvent = probeEvents.getProbeEvent(0);
+ assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType());
+ assertEquals(1234, probeEvent.getLatencyMicros());
+ assertEquals(ProbeResult.PR_SUCCESS, probeEvent.getProbeResult());
+ if (CaptivePortalDataShimImpl.isSupported()) {
+ assertTrue(probeEvent.hasCapportApiData());
+ // Set secondsRemaining to 3000 and check that getRemainingTtlSecs is within 10 seconds
+ final CapportApiData capportData = probeEvent.getCapportApiData();
+ assertTrue(capportData.getRemainingTtlSecs() <= secondsRemaining);
+ assertTrue(capportData.getRemainingTtlSecs() + TTL_TOLERANCE_SECS > secondsRemaining);
+ assertEquals(captivePortalData.getByteLimit() / 1000, capportData.getRemainingBytes());
+ } else {
+ assertFalse(probeEvent.hasCapportApiData());
+ }
+
+ // Verify the 2nd probe: ProbeType = PT_CAPPORT_API, Latency_us = 1234,
+ // ProbeResult = PR_SUCCESS, CapportApiData = null
+ probeEvent = probeEvents.getProbeEvent(1);
+ assertEquals(ProbeType.PT_CAPPORT_API, probeEvent.getProbeType());
+ assertEquals(1234, probeEvent.getLatencyMicros());
+ assertEquals(ProbeResult.PR_FAILURE, probeEvent.getProbeResult());
+ assertEquals(false, probeEvent.hasCapportApiData());
+
+ // Verify the 3rd probe: ProbeType = PT_DNS, Latency_us = 5678,
+ // ProbeResult = PR_FAILURE, CapportApiData = null
+ probeEvent = probeEvents.getProbeEvent(2);
+ assertEquals(ProbeType.PT_DNS, probeEvent.getProbeType());
+ assertEquals(5678, probeEvent.getLatencyMicros());
+ assertEquals(ProbeResult.PR_FAILURE, probeEvent.getProbeResult());
+ assertEquals(false, probeEvent.hasCapportApiData());
+
+ // Verify the 4th probe: ProbeType = PT_HTTP, Latency_us = longlatency,
+ // ProbeResult = PR_PORTAL, CapportApiData = null
+ probeEvent = probeEvents.getProbeEvent(3);
+ assertEquals(ProbeType.PT_HTTP, probeEvent.getProbeType());
+ // The latency exceeds Integer.MAX_VALUE(2147483647), it is limited to Integer.MAX_VALUE
+ assertEquals(Integer.MAX_VALUE, probeEvent.getLatencyMicros());
+ assertEquals(ProbeResult.PR_PORTAL, probeEvent.getProbeResult());
+ assertEquals(false, probeEvent.hasCapportApiData());
+
+ // Verify the ValidationResult
+ assertEquals(ValidationResult.VR_PARTIAL, stats.getValidationResult());
+ }
+}
diff --git a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
index 9de828f..c054b3a 100644
--- a/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
+++ b/tests/unit/src/com/android/server/NetworkStackServiceTest.kt
@@ -29,11 +29,11 @@
import android.net.dhcp.IDhcpServerCallbacks
import android.net.ip.IIpClientCallbacks
import android.net.ip.IpClient
-import android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH
import android.os.Build
import android.os.IBinder
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH
import com.android.server.NetworkStackService.Dependencies
import com.android.server.NetworkStackService.NetworkStackConnector
import com.android.server.NetworkStackService.PermissionChecker
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index f0a7bfb..a3ef532 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -52,6 +52,7 @@
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
+import static com.android.server.connectivity.NetworkMonitor.INITIAL_REEVALUATE_DELAY_MS;
import static com.android.server.connectivity.NetworkMonitor.extractCharset;
import static junit.framework.Assert.assertEquals;
@@ -68,6 +69,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.atLeastOnce;
@@ -257,6 +259,7 @@
private static final String TEST_LOGIN_URL = "https://testportal.example.com/login";
private static final String TEST_VENUE_INFO_URL = "https://venue.example.com/info";
private static final String TEST_SPEED_TEST_URL = "https://speedtest.example.com";
+ private static final String TEST_RELATIVE_URL = "/test/relative/gen_204";
private static final String TEST_MCCMNC = "123456";
private static final String[] TEST_HTTP_URLS = {TEST_HTTP_OTHER_URL1, TEST_HTTP_OTHER_URL2};
private static final String[] TEST_HTTPS_URLS = {TEST_HTTPS_OTHER_URL1, TEST_HTTPS_OTHER_URL2};
@@ -864,12 +867,15 @@
doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission(
eq(android.Manifest.permission.ACCESS_FINE_LOCATION), anyInt(), anyInt());
doReturn(new ContextWrapper(mContext)).when(mContext).createConfigurationContext(any());
+ doReturn(null).when(mTelephony).getAllCellInfo();
+ assertNull(wnm.getLocationMcc());
// Prepare CellInfo and check if the vote mechanism is working or not.
final List<CellInfo> cellList = new ArrayList<CellInfo>();
+ doReturn(cellList).when(mTelephony).getAllCellInfo();
+ assertNull(wnm.getLocationMcc());
cellList.add(makeTestCellInfoGsm("460"));
cellList.add(makeTestCellInfoGsm("460"));
cellList.add(makeTestCellInfoLte("466"));
- doReturn(cellList).when(mTelephony).getAllCellInfo();
// The count of 460 is 2 and the count of 466 is 1, so the getLocationMcc() should return
// 460.
assertEquals("460", wnm.getLocationMcc());
@@ -1112,10 +1118,13 @@
verify(mFallbackConnection, times(1)).getResponseCode();
verify(mOtherFallbackConnection, never()).getResponseCode();
- // Second check uses the URL chosen by Random
- final CaptivePortalProbeResult result = monitor.isCaptivePortal();
- assertTrue(result.isPortal());
- verify(mOtherFallbackConnection, times(1)).getResponseCode();
+ // Second check should be triggered automatically after the reevaluate delay, and uses the
+ // URL chosen by mRandom
+ // This test is appropriate to cover reevaluate behavior as long as the timeout is short
+ assertTrue(INITIAL_REEVALUATE_DELAY_MS < 2000);
+ verify(mOtherFallbackConnection, timeout(INITIAL_REEVALUATE_DELAY_MS + HANDLER_TIMEOUT_MS))
+ .getResponseCode();
+ verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
}
@Test
@@ -1143,7 +1152,29 @@
}
@Test
- public void testIsCaptivePortal_CapportApiIsPortal() throws Exception {
+ public void testIsCaptivePortal_CapportApiIsPortalWithNullPortalUrl() throws Exception {
+ assumeTrue(CaptivePortalDataShimImpl.isSupported());
+ setSslException(mHttpsConnection);
+ final long bytesRemaining = 10_000L;
+ final long secondsRemaining = 500L;
+ // Set content without partal url.
+ setApiContent(mCapportApiConnection, "{'captive': true,"
+ + "'venue-info-url': '" + TEST_VENUE_INFO_URL + "',"
+ + "'bytes-remaining': " + bytesRemaining + ","
+ + "'seconds-remaining': " + secondsRemaining + "}");
+ setPortal302(mHttpConnection);
+
+ runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES, VALIDATION_RESULT_PORTAL,
+ 0 /* probesSucceeded*/, TEST_LOGIN_URL);
+
+ verify(mCapportApiConnection).getResponseCode();
+
+ verify(mHttpConnection, times(1)).getResponseCode();
+ verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any());
+ }
+
+ @Test
+ public void testIsCaptivePortal_CapportApiIsPortalWithValidPortalUrl() throws Exception {
assumeTrue(CaptivePortalDataShimImpl.isSupported());
setSslException(mHttpsConnection);
final long bytesRemaining = 10_000L;
@@ -1198,6 +1229,32 @@
}
@Test
+ public void testIsCaptivePortal_NoRevalidationBeforeNetworkConnected() throws Exception {
+ assumeTrue(CaptivePortalDataShimImpl.isSupported());
+
+ final NetworkMonitor nm = makeCellMeteredNetworkMonitor();
+
+ final LinkProperties lp = makeCapportLPs();
+
+ // LinkProperties changed, but NM should not revalidate before notifyNetworkConnected
+ nm.notifyLinkPropertiesChanged(lp);
+ verify(mHttpConnection, after(100).never()).getResponseCode();
+ verify(mHttpsConnection, never()).getResponseCode();
+ verify(mCapportApiConnection, never()).getResponseCode();
+
+ setValidProbes();
+ setApiContent(mCapportApiConnection, "{'captive': true, "
+ + "'user-portal-url': '" + TEST_LOGIN_URL + "'}");
+
+ // After notifyNetworkConnected, validation uses the capport API contents
+ nm.notifyNetworkConnected(lp, CELL_METERED_CAPABILITIES);
+ verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+
+ verify(mHttpConnection, never()).getResponseCode();
+ verify(mCapportApiConnection).getResponseCode();
+ }
+
+ @Test
public void testIsCaptivePortal_CapportApiNotPortalNotValidated() throws Exception {
assumeTrue(CaptivePortalDataShimImpl.isSupported());
setSslException(mHttpsConnection);
@@ -1253,7 +1310,7 @@
@Test
public void testIsCaptivePortal_CapportApiInvalidContent() throws Exception {
assumeTrue(CaptivePortalDataShimImpl.isSupported());
- setStatus(mHttpsConnection, 204);
+ setSslException(mHttpsConnection);
setPortal302(mHttpConnection);
setApiContent(mCapportApiConnection, "{SomeInvalidText");
runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES,
@@ -1264,6 +1321,36 @@
verify(mHttpConnection).getResponseCode();
}
+ private void runCapportApiInvalidUrlTest(String url) throws Exception {
+ assumeTrue(CaptivePortalDataShimImpl.isSupported());
+ setSslException(mHttpsConnection);
+ setPortal302(mHttpConnection);
+ final LinkProperties lp = new LinkProperties(TEST_LINK_PROPERTIES);
+ lp.setCaptivePortalApiUrl(Uri.parse(url));
+ runNetworkTest(makeCapportLPs(), CELL_METERED_CAPABILITIES,
+ VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */,
+ TEST_LOGIN_URL);
+
+ verify(mCallbacks, never()).notifyCaptivePortalDataChanged(any());
+ verify(mCapportApiConnection, never()).getInputStream();
+ verify(mHttpConnection).getResponseCode();
+ }
+
+ @Test
+ public void testIsCaptivePortal_HttpIsInvalidCapportApiScheme() throws Exception {
+ runCapportApiInvalidUrlTest("http://capport.example.com");
+ }
+
+ @Test
+ public void testIsCaptivePortal_FileIsInvalidCapportApiScheme() throws Exception {
+ runCapportApiInvalidUrlTest("file://localhost/myfile");
+ }
+
+ @Test
+ public void testIsCaptivePortal_InvalidUrlFormat() throws Exception {
+ runCapportApiInvalidUrlTest("ThisIsNotAValidUrl");
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testIsCaptivePortal_CapportApiNotSupported() throws Exception {
// Test that on a R+ device, if NetworkStack was compiled without CaptivePortalData support
@@ -1805,10 +1892,11 @@
private void testDataStall_StallTcpSuspectedAndSendMetrics(int transport,
NetworkCapabilities nc) throws Exception {
assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q));
+ setupTcpDataStall();
// NM suspects data stall from TCP signal and sends data stall metrics.
setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_TCP);
final WrappedNetworkMonitor nm = prepareNetworkMonitorForVerifyDataStall(nc);
- setupTcpDataStall();
+
// Trigger a tcp event immediately.
setTcpPollingInterval(0);
nm.sendTcpPollingEvent();
@@ -2150,20 +2238,35 @@
@Test
public void testDismissPortalInValidatedNetworkEnabledOsSupported() throws Exception {
assumeTrue(ShimUtils.isAtLeastR());
- testDismissPortalInValidatedNetworkEnabled(TEST_LOGIN_URL);
+ testDismissPortalInValidatedNetworkEnabled(TEST_LOGIN_URL, TEST_LOGIN_URL);
+ }
+
+ @Test
+ public void testDismissPortalInValidatedNetworkEnabledOsSupported_NullLocationUrl()
+ throws Exception {
+ assumeTrue(ShimUtils.isAtLeastR());
+ testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, null /* locationUrl */);
+ }
+
+ @Test
+ public void testDismissPortalInValidatedNetworkEnabledOsSupported_InvalidLocationUrl()
+ throws Exception {
+ assumeTrue(ShimUtils.isAtLeastR());
+ testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, TEST_RELATIVE_URL);
}
@Test
public void testDismissPortalInValidatedNetworkEnabledOsNotSupported() throws Exception {
assumeFalse(ShimUtils.isAtLeastR());
- testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL);
+ testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL, TEST_LOGIN_URL);
}
- private void testDismissPortalInValidatedNetworkEnabled(String portalUrl) throws Exception {
+ private void testDismissPortalInValidatedNetworkEnabled(String expectedUrl, String locationUrl)
+ throws Exception {
setDismissPortalInValidatedNetwork(true);
setSslException(mHttpsConnection);
setPortal302(mHttpConnection);
- when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(TEST_LOGIN_URL);
+ when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(locationUrl);
final NetworkMonitor nm = makeMonitor(CELL_METERED_CAPABILITIES);
notifyNetworkConnected(nm, CELL_METERED_CAPABILITIES);
@@ -2188,7 +2291,7 @@
assertEquals(TEST_NETID, networkCaptor.getValue().netId);
// Portal URL should be redirect URL.
final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
- assertEquals(portalUrl, redirectUrl);
+ assertEquals(expectedUrl, redirectUrl);
}
@Test
diff --git a/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index 533bbc3..c0bdc4c 100644
--- a/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -33,6 +34,7 @@
import android.net.ipmemorystore.IOnL2KeyResponseListener;
import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusAndCountListener;
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
@@ -44,6 +46,7 @@
import android.os.IBinder;
import android.os.RemoteException;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -69,10 +72,13 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/** Unit tests for {@link IpMemoryStoreService}. */
@@ -198,6 +204,32 @@
};
}
+ /** Helper method to make a vanilla IOnStatusAndCountListener */
+ private IOnStatusAndCountListener onDeleteStatus(BiConsumer<Status, Integer> functor) {
+ return new IOnStatusAndCountListener() {
+ @Override
+ public void onComplete(final StatusParcelable statusParcelable, final int deletedCount)
+ throws RemoteException {
+ functor.accept(new Status(statusParcelable), deletedCount);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return this.HASH;
+ }
+ };
+ }
+
/** Helper method to make an IOnBlobRetrievedListener */
private interface OnBlobRetrievedListener {
void onBlobRetrieved(Status status, String l2Key, String name, byte[] data);
@@ -339,17 +371,18 @@
}
}
- // Helper method to store network attributes to database
- private void storeAttributes(final String l2Key, final NetworkAttributes na) {
- storeAttributes("Did not complete storing attributes", l2Key, na);
+ // Helper method to store network attributes to database. Returns the stored attributes.
+ private NetworkAttributes storeAttributes(final String l2Key, final NetworkAttributes na) {
+ return storeAttributes("Did not complete storing attributes", l2Key, na);
}
- private void storeAttributes(final String timeoutMessage, final String l2Key,
+ private NetworkAttributes storeAttributes(final String timeoutMessage, final String l2Key,
final NetworkAttributes na) {
doLatched(timeoutMessage, latch -> mService.storeNetworkAttributes(l2Key, na.toParcelable(),
onStatus(status -> {
assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
latch.countDown();
})));
+ return na;
}
// Helper method to store blob data to database
@@ -546,28 +579,37 @@
})));
}
- @Test
- public void testFindL2Key() throws UnknownHostException {
+ private List<NetworkAttributes> storeFixture() throws Exception {
+ final ArrayList<NetworkAttributes> stored = new ArrayList<>();
final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
na.setCluster("cluster0");
- storeAttributes(FAKE_KEYS[0], na.build());
+ stored.add(storeAttributes(FAKE_KEYS[0], na.build()));
na.setDnsAddresses(Arrays.asList(
new InetAddress[] {Inet6Address.getByName("8D56:9AF1::08EE:20F1")}));
- na.setMtu(219);
- storeAttributes(FAKE_KEYS[1], na.build());
+ na.setMtu(208);
+ stored.add(storeAttributes(FAKE_KEYS[1], na.build()));
na.setMtu(null);
na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
na.setDnsAddresses(Arrays.asList(
new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
na.setCluster("cluster1");
- storeAttributes(FAKE_KEYS[2], na.build());
+ stored.add(storeAttributes(FAKE_KEYS[2], na.build()));
na.setMtu(219);
- storeAttributes(FAKE_KEYS[3], na.build());
+ stored.add(storeAttributes(FAKE_KEYS[3], na.build()));
+ na.setCluster(null);
na.setMtu(240);
- storeAttributes(FAKE_KEYS[4], na.build());
+ stored.add(storeAttributes(FAKE_KEYS[4], na.build()));
na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("5.6.7.8"));
- storeAttributes(FAKE_KEYS[5], na.build());
+ stored.add(storeAttributes(FAKE_KEYS[5], na.build()));
+ return stored;
+ }
+
+ @Test
+ public void testFindL2Key() throws Exception {
+ final List<NetworkAttributes> stored = storeFixture();
+ final NetworkAttributes.Builder na = new NetworkAttributes.Builder(
+ stored.get(stored.size() - 1));
// Matches key 5 exactly
doLatched("Did not finish finding L2Key", latch ->
@@ -589,6 +631,7 @@
})));
// Closest to key 3 (indeed, identical)
+ na.setCluster("cluster1");
na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
na.setMtu(219);
doLatched("Did not finish finding L2Key", latch ->
@@ -620,13 +663,13 @@
latch.countDown();
})));
- // But changing the MTU makes this closer to key 4
- na.setMtu(240);
+ // But changing the MTU makes this closer to key 2
+ na.setMtu(208);
doLatched("Did not finish finding L2Key", latch ->
mService.findL2Key(na.build().toParcelable(), onL2KeyResponse((status, key) -> {
assertTrue("Retrieve network sameness not successful : " + status.resultCode,
status.isSuccess());
- assertEquals(FAKE_KEYS[4], key);
+ assertEquals(FAKE_KEYS[2], key);
latch.countDown();
})));
@@ -691,6 +734,63 @@
})));
}
+ private NetworkAttributes fetchAttributes(@NonNull final String l2Key) throws Exception {
+ final CompletableFuture<NetworkAttributes> f = new CompletableFuture<>();
+ mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ f.complete(attr);
+ }));
+ return f.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ private void delete(@NonNull final String l2Key) {
+ doLatched("Did not finish deleting", latch ->
+ mService.delete(l2Key, false /* needWipe */, onDeleteStatus((status, deleted) -> {
+ assertTrue("Deleting failed :" + status.resultCode, status.isSuccess());
+ assertEquals("Deleting count != 1 :" + deleted, 1, deleted.intValue());
+ latch.countDown();
+ })), LONG_TIMEOUT_MS);
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ storeFixture();
+
+ delete(FAKE_KEYS[0]);
+ delete(FAKE_KEYS[3]);
+
+ assertNull(fetchAttributes(FAKE_KEYS[0]));
+ assertNotNull(fetchAttributes(FAKE_KEYS[1]));
+ assertNotNull(fetchAttributes(FAKE_KEYS[2]));
+ assertNull(fetchAttributes(FAKE_KEYS[3]));
+ assertNotNull(fetchAttributes(FAKE_KEYS[4]));
+ assertNotNull(fetchAttributes(FAKE_KEYS[5]));
+ }
+
+ @Test
+ public void testDeleteCluster() throws Exception {
+ storeFixture();
+
+ doLatched("Did not finish deleting", latch ->
+ mService.deleteCluster("cluster1", false /* needWipe */,
+ onDeleteStatus((status, deletedCount) -> {
+ assertTrue("Delete failed : " + status.resultCode, status.isSuccess());
+ // The fixture stores 2 keys under "cluster1"
+ assertEquals("Unexpected deleted count : " + deletedCount,
+ 2, deletedCount.intValue());
+ latch.countDown();
+ })), LONG_TIMEOUT_MS);
+
+ assertNotNull(fetchAttributes(FAKE_KEYS[0]));
+ assertNotNull(fetchAttributes(FAKE_KEYS[1]));
+ assertNull(fetchAttributes(FAKE_KEYS[2]));
+ assertNull(fetchAttributes(FAKE_KEYS[3]));
+ assertNotNull(fetchAttributes(FAKE_KEYS[4]));
+ assertNotNull(fetchAttributes(FAKE_KEYS[5]));
+ }
+
@Test
public void testFullMaintenance() throws Exception {
copyTestData(mDbFile);